Browse Source

Add Avalonia.FreeDesktop.AtSpi as Linux's AutomationNode skeleton project based on Avalonia.DBus/AtSpi2TestApp

features/dbus+atspi2+kdeappiumdriver
Jumar Macato 1 month ago
parent
commit
671367ae10
  1. 2
      Avalonia.sln
  2. 61
      src/Avalonia.FreeDesktop.AtSpi/AtSpiCacheHandler.cs
  3. 246
      src/Avalonia.FreeDesktop.AtSpi/AtSpiConstants.cs
  4. 103
      src/Avalonia.FreeDesktop.AtSpi/AtSpiNode.RoleMapping.cs
  5. 83
      src/Avalonia.FreeDesktop.AtSpi/AtSpiNode.StateMapping.cs
  6. 135
      src/Avalonia.FreeDesktop.AtSpi/AtSpiNode.cs
  7. 56
      src/Avalonia.FreeDesktop.AtSpi/AtSpiNodeHandlers.cs
  8. 392
      src/Avalonia.FreeDesktop.AtSpi/AtSpiServer.cs
  9. 38
      src/Avalonia.FreeDesktop.AtSpi/Avalonia.FreeDesktop.AtSpi.csproj
  10. 883
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/Accessible.xml
  11. 145
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/Action.xml
  12. 86
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/Application.xml
  13. 119
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/Cache.xml
  14. 346
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/Component.xml
  15. 148
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/DeviceEventController.xml
  16. 799
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/Event.xml
  17. 87
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/Image.xml
  18. 131
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/Registry.xml
  19. 102
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/Socket.xml
  20. 158
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/Types.xml
  21. 47
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/Value.xml
  22. 9
      src/Avalonia.FreeDesktop.AtSpi/DBusXml/org.a11y.Bus.xml
  23. 134
      src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiAccessibleHandler.cs
  24. 112
      src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiActionHandler.cs
  25. 39
      src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiApplicationHandler.cs
  26. 219
      src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiComponentHandler.cs
  27. 57
      src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiEventObjectHandler.cs
  28. 41
      src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiValueHandler.cs
  29. 46
      src/Avalonia.FreeDesktop.AtSpi/RootAtSpiNode.cs

2
Avalonia.sln

@ -159,6 +159,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop.AtSpi", "src\Avalonia.FreeDesktop.AtSpi\Avalonia.FreeDesktop.AtSpi.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless", "src\Headless\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"

61
src/Avalonia.FreeDesktop.AtSpi/AtSpiCacheHandler.cs

@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.DBus;
using Avalonia.FreeDesktop.AtSpi.DBusXml;
using static Avalonia.FreeDesktop.AtSpi.AtSpiConstants;
namespace Avalonia.FreeDesktop.AtSpi
{
internal sealed class AtSpiCacheHandler : IOrgA11yAtspiCache
{
private readonly AtSpiServer _server;
public AtSpiCacheHandler(AtSpiServer server)
{
_server = server;
}
public uint Version => CacheVersion;
public ValueTask<List<AtSpiAccessibleCacheItem>> GetItemsAsync()
{
List<AtSpiNode> snapshot;
lock (_server.TreeGate)
{
snapshot = _server.GetAllNodes()
.OrderBy(static node => node.Path, System.StringComparer.Ordinal)
.ToList();
}
var items = new List<AtSpiAccessibleCacheItem>(snapshot.Count);
items.AddRange(snapshot.Select(n => _server.BuildCacheItem(n)));
return ValueTask.FromResult(items);
}
public void EmitAddAccessibleSignal(AtSpiAccessibleCacheItem item)
{
EmitSignal("AddAccessible", item);
}
public void EmitRemoveAccessibleSignal(AtSpiObjectReference node)
{
EmitSignal("RemoveAccessible", node);
}
private void EmitSignal(string member, params object[] body)
{
var connection = _server.A11yConnection;
if (connection is null)
return;
var message = DBusMessage.CreateSignal(
(DBusObjectPath)CachePath,
IfaceCache,
member,
body);
_ = connection.SendMessageAsync(message);
}
}
}

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

@ -0,0 +1,246 @@
using System.Collections.Generic;
using System.Globalization;
namespace Avalonia.FreeDesktop.AtSpi
{
/// <summary>
/// AT-SPI2 role IDs from atspi-constants.h.
/// </summary>
internal enum AtSpiRole
{
Invalid = 0,
Accelerator = 1,
Alert = 2,
Animation = 3,
Arrow = 4,
Calendar = 5,
Canvas = 6,
CheckBox = 7,
CheckMenuItem = 8,
ColorChooser = 9,
ColumnHeader = 10,
ComboBox = 11,
DateEditor = 12,
DesktopIcon = 13,
DesktopFrame = 14,
Dial = 15,
Dialog = 16,
DirectoryPane = 17,
DrawingArea = 18,
FileChooser = 19,
Filler = 20,
FocusTraversable = 21,
Frame = 23,
GlassPane = 24,
HtmlContainer = 25,
Icon = 26,
Image = 27,
InternalFrame = 28,
Label = 29,
LayeredPane = 30,
List = 31,
ListItem = 32,
Menu = 33,
MenuBar = 34,
MenuItem = 35,
OptionPane = 36,
PageTab = 37,
PageTabList = 38,
Panel = 39,
PasswordText = 40,
PopupMenu = 41,
ProgressBar = 42,
PushButton = 43,
RadioButton = 44,
RadioMenuItem = 45,
RootPane = 46,
RowHeader = 47,
ScrollBar = 48,
ScrollPane = 49,
Separator = 50,
Slider = 51,
SpinButton = 52,
SplitPane = 53,
StatusBar = 54,
Table = 55,
TableCell = 56,
TableColumnHeader = 57,
TableRowHeader = 58,
TearoffMenuItem = 59,
Terminal = 60,
Text = 61,
ToggleButton = 62,
ToolBar = 63,
ToolTip = 64,
Tree = 65,
TreeTable = 66,
Unknown = 67,
Viewport = 68,
Window = 69,
Extended = 70,
Header = 71,
Footer = 72,
Paragraph = 73,
Ruler = 74,
Application = 75,
AutoComplete = 76,
EditBar = 77,
Embedded = 78,
Entry = 79,
Heading = 81,
Page = 82,
Document = 83,
Section = 84,
RedundantObject = 85,
Form = 86,
Link = 87,
InputMethodWindow = 88,
TableRow = 89,
TreeItem = 90,
DocumentSpreadsheet = 91,
DocumentPresentation = 92,
DocumentText = 93,
DocumentWeb = 94,
DocumentEmail = 95,
Comment = 96,
ListBox = 97,
Grouping = 98,
ImageMap = 99,
Notification = 100,
InfoBar = 101,
LevelBar = 102,
TitleBar = 103,
BlockQuote = 104,
Audio = 105,
Video = 106,
Definition = 107,
Article = 108,
Landmark = 109,
Log = 110,
Marquee = 111,
Math = 112,
Rating = 113,
Timer = 114,
Static = 116,
MathFraction = 117,
MathRoot = 118,
Subscript = 119,
Superscript = 120,
}
/// <summary>
/// AT-SPI2 state IDs from atspi-constants.h.
/// </summary>
internal enum AtSpiState : uint
{
Invalid = 0,
Active = 1,
Armed = 2,
Busy = 3,
Checked = 4,
Collapsed = 5,
Defunct = 6,
Editable = 7,
Enabled = 8,
Expandable = 9,
Expanded = 10,
Focusable = 11,
Focused = 12,
HasToolTip = 13,
Horizontal = 14,
Iconified = 15,
Modal = 16,
MultiLine = 17,
MultiSelectable = 18,
Opaque = 19,
Pressed = 20,
Resizable = 21,
Selectable = 22,
Selected = 23,
Sensitive = 24,
Showing = 25,
SingleLine = 26,
Stale = 27,
Transient = 28,
Vertical = 29,
Visible = 30,
ManagesDescendants = 31,
Indeterminate = 32,
Required = 33,
Truncated = 34,
Animated = 35,
InvalidEntry = 36,
SupportsAutoCompletion = 37,
SelectableText = 38,
IsDefault = 39,
Visited = 40,
Checkable = 41,
HasPopup = 42,
ReadOnly = 43,
}
internal static class AtSpiConstants
{
// D-Bus paths
internal const string RootPath = "/org/a11y/atspi/accessible/root";
internal const string CachePath = "/org/a11y/atspi/cache";
internal const string NullPath = "/org/a11y/atspi/null";
internal const string AppPathPrefix = "/org/avalonia/a11y";
internal const string RegistryPath = "/org/a11y/atspi/registry";
// Interface names
internal const string IfaceAccessible = "org.a11y.atspi.Accessible";
internal const string IfaceApplication = "org.a11y.atspi.Application";
internal const string IfaceComponent = "org.a11y.atspi.Component";
internal const string IfaceAction = "org.a11y.atspi.Action";
internal const string IfaceValue = "org.a11y.atspi.Value";
internal const string IfaceEventObject = "org.a11y.atspi.Event.Object";
internal const string IfaceCache = "org.a11y.atspi.Cache";
// Bus names
internal const string BusNameRegistry = "org.a11y.atspi.Registry";
internal const string BusNameA11y = "org.a11y.Bus";
internal const string PathA11y = "/org/a11y/bus";
// Interface versions
internal const uint AccessibleVersion = 1;
internal const uint ApplicationVersion = 1;
internal const uint ComponentVersion = 1;
internal const uint ActionVersion = 1;
internal const uint ValueVersion = 1;
internal const uint EventObjectVersion = 1;
internal const uint CacheVersion = 1;
internal static List<uint> BuildStateSet(IReadOnlyCollection<AtSpiState> states)
{
if (states == null || states.Count == 0)
return new List<uint> { 0u, 0u };
uint low = 0;
uint high = 0;
foreach (var state in states)
{
var bit = (uint)state;
if (bit < 32)
low |= 1u << (int)bit;
else if (bit < 64)
high |= 1u << (int)(bit - 32);
}
return new List<uint> { low, high };
}
internal static string ResolveLocale()
{
var culture = CultureInfo.CurrentUICulture.Name;
if (string.IsNullOrWhiteSpace(culture))
culture = "en_US";
return culture.Replace('-', '_');
}
internal static string ResolveToolkitVersion()
{
return typeof(AtSpiConstants).Assembly.GetName().Version?.ToString() ?? "0";
}
}
}

103
src/Avalonia.FreeDesktop.AtSpi/AtSpiNode.RoleMapping.cs

@ -0,0 +1,103 @@
using Avalonia.Automation.Peers;
namespace Avalonia.FreeDesktop.AtSpi
{
internal partial class AtSpiNode
{
public static AtSpiRole ToAtSpiRole(AutomationControlType controlType)
{
return controlType switch
{
AutomationControlType.None => AtSpiRole.Panel,
AutomationControlType.Button => AtSpiRole.PushButton,
AutomationControlType.Calendar => AtSpiRole.Calendar,
AutomationControlType.CheckBox => AtSpiRole.CheckBox,
AutomationControlType.ComboBox => AtSpiRole.ComboBox,
AutomationControlType.ComboBoxItem => AtSpiRole.ListItem,
AutomationControlType.Edit => AtSpiRole.Entry,
AutomationControlType.Hyperlink => AtSpiRole.Link,
AutomationControlType.Image => AtSpiRole.Image,
AutomationControlType.ListItem => AtSpiRole.ListItem,
AutomationControlType.List => AtSpiRole.List,
AutomationControlType.Menu => AtSpiRole.Menu,
AutomationControlType.MenuBar => AtSpiRole.MenuBar,
AutomationControlType.MenuItem => AtSpiRole.MenuItem,
AutomationControlType.ProgressBar => AtSpiRole.ProgressBar,
AutomationControlType.RadioButton => AtSpiRole.RadioButton,
AutomationControlType.ScrollBar => AtSpiRole.ScrollBar,
AutomationControlType.Slider => AtSpiRole.Slider,
AutomationControlType.Spinner => AtSpiRole.SpinButton,
AutomationControlType.StatusBar => AtSpiRole.StatusBar,
AutomationControlType.Tab => AtSpiRole.PageTabList,
AutomationControlType.TabItem => AtSpiRole.PageTab,
AutomationControlType.Text => AtSpiRole.Label,
AutomationControlType.ToolBar => AtSpiRole.ToolBar,
AutomationControlType.ToolTip => AtSpiRole.ToolTip,
AutomationControlType.Tree => AtSpiRole.Tree,
AutomationControlType.TreeItem => AtSpiRole.TreeItem,
AutomationControlType.Custom => AtSpiRole.Unknown,
AutomationControlType.Group => AtSpiRole.Panel,
AutomationControlType.Thumb => AtSpiRole.PushButton,
AutomationControlType.DataGrid => AtSpiRole.TreeTable,
AutomationControlType.DataItem => AtSpiRole.TableCell,
AutomationControlType.Document => AtSpiRole.Document,
AutomationControlType.SplitButton => AtSpiRole.PushButton,
AutomationControlType.Window => AtSpiRole.Frame,
AutomationControlType.Pane => AtSpiRole.Panel,
AutomationControlType.Header => AtSpiRole.Header,
AutomationControlType.HeaderItem => AtSpiRole.ColumnHeader,
AutomationControlType.Table => AtSpiRole.Table,
AutomationControlType.TitleBar => AtSpiRole.TitleBar,
AutomationControlType.Separator => AtSpiRole.Separator,
AutomationControlType.Expander => AtSpiRole.Panel,
_ => AtSpiRole.Unknown,
};
}
public static string ToAtSpiRoleName(AtSpiRole role)
{
return role switch
{
AtSpiRole.Application => "application",
AtSpiRole.Frame => "frame",
AtSpiRole.PushButton => "push button",
AtSpiRole.CheckBox => "check box",
AtSpiRole.ComboBox => "combo box",
AtSpiRole.Entry => "entry",
AtSpiRole.Label => "label",
AtSpiRole.Image => "image",
AtSpiRole.List => "list",
AtSpiRole.ListItem => "list item",
AtSpiRole.Menu => "menu",
AtSpiRole.MenuBar => "menu bar",
AtSpiRole.MenuItem => "menu item",
AtSpiRole.ProgressBar => "progress bar",
AtSpiRole.RadioButton => "radio button",
AtSpiRole.ScrollBar => "scroll bar",
AtSpiRole.Slider => "slider",
AtSpiRole.SpinButton => "spin button",
AtSpiRole.StatusBar => "status bar",
AtSpiRole.PageTab => "page tab",
AtSpiRole.PageTabList => "page tab list",
AtSpiRole.ToolBar => "tool bar",
AtSpiRole.ToolTip => "tool tip",
AtSpiRole.Tree => "tree",
AtSpiRole.TreeItem => "tree item",
AtSpiRole.Panel => "panel",
AtSpiRole.Separator => "separator",
AtSpiRole.Table => "table",
AtSpiRole.TableCell => "table cell",
AtSpiRole.TreeTable => "tree table",
AtSpiRole.ColumnHeader => "column header",
AtSpiRole.Header => "header",
AtSpiRole.TitleBar => "title bar",
AtSpiRole.Document => "document frame",
AtSpiRole.Link => "link",
AtSpiRole.Calendar => "calendar",
AtSpiRole.Window => "window",
AtSpiRole.Unknown => "unknown",
_ => "unknown",
};
}
}
}

83
src/Avalonia.FreeDesktop.AtSpi/AtSpiNode.StateMapping.cs

@ -0,0 +1,83 @@
using System.Collections.Generic;
using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Provider;
using static Avalonia.FreeDesktop.AtSpi.AtSpiConstants;
namespace Avalonia.FreeDesktop.AtSpi
{
internal partial class AtSpiNode
{
public List<uint> ComputeStates()
{
return InvokeSync(() => ComputeStatesCore());
}
private List<uint> ComputeStatesCore()
{
var states = new HashSet<AtSpiState>();
if (Peer.IsEnabled())
{
states.Add(AtSpiState.Enabled);
states.Add(AtSpiState.Sensitive);
}
if (!Peer.IsOffscreen())
{
states.Add(AtSpiState.Visible);
states.Add(AtSpiState.Showing);
}
if (Peer.IsKeyboardFocusable())
states.Add(AtSpiState.Focusable);
if (Peer.HasKeyboardFocus())
states.Add(AtSpiState.Focused);
// Toggle state
if (Peer.GetProvider<IToggleProvider>() is { } toggle)
{
states.Add(AtSpiState.Checkable);
switch (toggle.ToggleState)
{
case ToggleState.On:
states.Add(AtSpiState.Checked);
break;
case ToggleState.Indeterminate:
states.Add(AtSpiState.Indeterminate);
break;
}
}
// Expand/collapse state
if (Peer.GetProvider<IExpandCollapseProvider>() is { } expandCollapse)
{
states.Add(AtSpiState.Expandable);
switch (expandCollapse.ExpandCollapseState)
{
case ExpandCollapseState.Expanded:
states.Add(AtSpiState.Expanded);
break;
case ExpandCollapseState.Collapsed:
states.Add(AtSpiState.Collapsed);
break;
}
}
// Range value read-only
if (Peer.GetProvider<IRangeValueProvider>() is { } rangeValue)
{
if (rangeValue.IsReadOnly)
states.Add(AtSpiState.ReadOnly);
}
// Window-level active state
var controlType = Peer.GetAutomationControlType();
if (controlType == AutomationControlType.Window)
states.Add(AtSpiState.Active);
return BuildStateSet(states);
}
}
}

135
src/Avalonia.FreeDesktop.AtSpi/AtSpiNode.cs

@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Provider;
using Avalonia.Threading;
using static Avalonia.FreeDesktop.AtSpi.AtSpiConstants;
namespace Avalonia.FreeDesktop.AtSpi
{
internal partial class AtSpiNode
{
private static readonly ConditionalWeakTable<AutomationPeer, AtSpiNode> s_nodes = new();
private static int s_nextId;
private readonly string _path;
public AtSpiNode(AutomationPeer peer, AtSpiServer server)
{
Peer = peer;
Server = server;
_path = $"{AppPathPrefix}/{s_nextId++}";
s_nodes.Add(peer, this);
peer.ChildrenChanged += OnPeerChildrenChanged;
peer.PropertyChanged += OnPeerPropertyChanged;
}
protected AtSpiNode(AutomationPeer peer, AtSpiServer server, string path)
{
Peer = peer;
Server = server;
_path = path;
s_nodes.Add(peer, this);
peer.ChildrenChanged += OnPeerChildrenChanged;
peer.PropertyChanged += OnPeerPropertyChanged;
}
public AutomationPeer Peer { get; }
public AtSpiServer Server { get; }
public string Path => _path;
public AtSpiNodeHandlers? Handlers { get; set; }
public HashSet<string> GetSupportedInterfaces()
{
var interfaces = new HashSet<string>(StringComparer.Ordinal) { IfaceAccessible };
if (Peer.GetProvider<IRootProvider>() is not null)
interfaces.Add(IfaceApplication);
// All visual elements support Component
interfaces.Add(IfaceComponent);
if (Peer.GetProvider<IInvokeProvider>() is not null ||
Peer.GetProvider<IToggleProvider>() is not null ||
Peer.GetProvider<IExpandCollapseProvider>() is not null)
{
interfaces.Add(IfaceAction);
}
if (Peer.GetProvider<IRangeValueProvider>() is not null)
interfaces.Add(IfaceValue);
return interfaces;
}
#if NET6_0_OR_GREATER
[return: NotNullIfNotNull(nameof(peer))]
#endif
public static AtSpiNode? GetOrCreate(AutomationPeer? peer, AtSpiServer server)
{
if (peer is null)
return null;
return s_nodes.GetValue(peer, p =>
{
if (p.GetProvider<IRootProvider>() is not null)
return new RootAtSpiNode(p, server);
return new AtSpiNode(p, server);
});
}
public static AtSpiNode? TryGet(AutomationPeer? peer)
{
if (peer is null)
return null;
s_nodes.TryGetValue(peer, out var node);
return node;
}
public static void Release(AutomationPeer peer) => s_nodes.Remove(peer);
internal void InvokeSync(Action action)
{
if (Dispatcher.UIThread.CheckAccess())
action();
else
Dispatcher.UIThread.InvokeAsync(action).Wait();
}
internal T InvokeSync<T>(Func<T> func)
{
if (Dispatcher.UIThread.CheckAccess())
return func();
else
return Dispatcher.UIThread.InvokeAsync(func).Result;
}
internal TResult InvokeSync<TInterface, TResult>(Func<TInterface, TResult> func)
{
if (Peer.GetProvider<TInterface>() is TInterface provider)
return InvokeSync(() => func(provider));
throw new NotSupportedException($"Peer does not implement {typeof(TInterface).Name}.");
}
internal void InvokeSync<TInterface>(Action<TInterface> action)
{
if (Peer.GetProvider<TInterface>() is TInterface provider)
InvokeSync(() => action(provider));
else
throw new NotSupportedException($"Peer does not implement {typeof(TInterface).Name}.");
}
private void OnPeerChildrenChanged(object? sender, EventArgs e)
{
Server.EmitChildrenChanged(this);
}
private void OnPeerPropertyChanged(object? sender, AutomationPropertyChangedEventArgs e)
{
Server.EmitPropertyChange(this, e);
}
}
}

56
src/Avalonia.FreeDesktop.AtSpi/AtSpiNodeHandlers.cs

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Avalonia.DBus;
using Avalonia.FreeDesktop.AtSpi.Handlers;
namespace Avalonia.FreeDesktop.AtSpi
{
internal sealed class AtSpiNodeHandlers
{
public AtSpiNodeHandlers(AtSpiNode node)
{
Node = node;
}
public AtSpiNode Node { get; }
public AtSpiAccessibleHandler? AccessibleHandler { get; set; }
public AtSpiApplicationHandler? ApplicationHandler { get; set; }
public AtSpiComponentHandler? ComponentHandler { get; set; }
public AtSpiActionHandler? ActionHandler { get; set; }
public AtSpiValueHandler? ValueHandler { get; set; }
public AtSpiEventObjectHandler? EventObjectHandler { get; set; }
public IDisposable Register(
IDBusConnection connection,
SynchronizationContext? synchronizationContext = null)
{
ArgumentNullException.ThrowIfNull(connection);
var targets = new List<object>();
if (AccessibleHandler != null)
targets.Add(AccessibleHandler);
if (ApplicationHandler != null)
targets.Add(ApplicationHandler);
if (ComponentHandler != null)
targets.Add(ComponentHandler);
if (ActionHandler != null)
targets.Add(ActionHandler);
if (ValueHandler != null)
targets.Add(ValueHandler);
if (EventObjectHandler != null)
targets.Add(EventObjectHandler);
if (targets.Count == 0)
return EmptyRegistration.Instance;
return connection.RegisterObjects((DBusObjectPath)Node.Path, targets, synchronizationContext);
}
private sealed class EmptyRegistration : IDisposable
{
public static EmptyRegistration Instance { get; } = new();
public void Dispose() { }
}
}
}

392
src/Avalonia.FreeDesktop.AtSpi/AtSpiServer.cs

@ -0,0 +1,392 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Provider;
using Avalonia.DBus;
using Avalonia.FreeDesktop.AtSpi.DBusXml;
using Avalonia.FreeDesktop.AtSpi.Handlers;
using static Avalonia.FreeDesktop.AtSpi.AtSpiConstants;
namespace Avalonia.FreeDesktop.AtSpi
{
internal sealed class AtSpiServer : IAsyncDisposable
{
private readonly object _treeGate = new();
private readonly Dictionary<string, AtSpiNode> _nodesByPath = new(StringComparer.Ordinal);
private readonly Dictionary<string, ActivePathRegistration> _pathRegistrations = new(StringComparer.Ordinal);
private DBusConnection? _a11yConnection;
private string _uniqueName = string.Empty;
private AtSpiCacheHandler? _cacheHandler;
private RootAtSpiNode? _rootNode;
internal DBusConnection? A11yConnection => _a11yConnection;
internal object TreeGate => _treeGate;
public async Task StartAsync(AutomationPeer rootPeer)
{
// 1. Get a11y bus address
var address = await GetAccessibilityBusAddressAsync();
if (string.IsNullOrWhiteSpace(address))
throw new InvalidOperationException("Failed to resolve the accessibility bus address.");
// 2. Connect to a11y bus
_a11yConnection = await DBusConnection.ConnectAsync(address);
_uniqueName = await _a11yConnection.GetUniqueNameAsync() ?? string.Empty;
// 3. Create root node
_rootNode = new RootAtSpiNode(rootPeer, this);
RegisterNode(_rootNode);
// 4. Walk peer tree
WalkAndCreateNodes(rootPeer);
// 5. Build handlers for all nodes
BuildHandlers();
// 6. Register D-Bus object paths
RefreshPathRegistrations();
// 7. Embed with registry
await EmbedApplicationAsync();
// 8. Emit initial cache snapshot
EmitInitialCacheSnapshot();
}
public async ValueTask DisposeAsync()
{
foreach (var registration in _pathRegistrations.Values)
registration.Registration.Dispose();
_pathRegistrations.Clear();
if (_a11yConnection is not null)
{
await _a11yConnection.DisposeAsync();
_a11yConnection = null;
}
_uniqueName = string.Empty;
_cacheHandler = null;
_rootNode = null;
_nodesByPath.Clear();
}
internal void RegisterNode(AtSpiNode node)
{
lock (_treeGate)
{
_nodesByPath[node.Path] = node;
}
}
internal void UnregisterNode(AtSpiNode node)
{
lock (_treeGate)
{
_nodesByPath.Remove(node.Path);
}
}
internal IReadOnlyCollection<AtSpiNode> GetAllNodes()
{
lock (_treeGate)
{
return _nodesByPath.Values.ToArray();
}
}
internal AtSpiObjectReference GetReference(AtSpiNode? node)
{
if (node is null)
return GetNullReference();
return new AtSpiObjectReference(_uniqueName, new DBusObjectPath(node.Path));
}
internal AtSpiObjectReference GetNullReference()
{
return new AtSpiObjectReference(string.Empty, new DBusObjectPath(NullPath));
}
internal AtSpiObjectReference GetRootReference()
{
return new AtSpiObjectReference(_uniqueName, new DBusObjectPath(RootPath));
}
internal AtSpiAccessibleCacheItem BuildCacheItem(AtSpiNode node)
{
var self = new AtSpiObjectReference(_uniqueName, new DBusObjectPath(node.Path));
var app = new AtSpiObjectReference(_uniqueName, new DBusObjectPath(RootPath));
var parentPeer = node.InvokeSync(() => node.Peer.GetParent());
var parentNode = AtSpiNode.TryGet(parentPeer);
var parent = parentNode is not null
? new AtSpiObjectReference(_uniqueName, new DBusObjectPath(parentNode.Path))
: new AtSpiObjectReference(string.Empty, new DBusObjectPath(NullPath));
var indexInParent = node.InvokeSync(() =>
{
var p = node.Peer.GetParent();
if (p is null) return -1;
var children = p.GetChildren();
for (var i = 0; i < children.Count; i++)
{
if (ReferenceEquals(children[i], node.Peer))
return i;
}
return -1;
});
var childCount = node.InvokeSync(() => node.Peer.GetChildren().Count);
var interfaces = node.InvokeSync(() => node.GetSupportedInterfaces())
.OrderBy(static i => i, StringComparer.Ordinal)
.ToList();
var name = node.InvokeSync(() => node.Peer.GetName());
var role = (uint)node.InvokeSync(() => AtSpiNode.ToAtSpiRole(node.Peer.GetAutomationControlType()));
var description = node.InvokeSync(() => node.Peer.GetHelpText());
var states = node.ComputeStates();
return new AtSpiAccessibleCacheItem(
self,
app,
parent,
indexInParent,
childCount,
interfaces,
name,
role,
description,
states);
}
internal void EmitChildrenChanged(AtSpiNode node)
{
if (_a11yConnection is null || _cacheHandler is null)
return;
// Re-walk the changed subtree to pick up new nodes
var peer = node.Peer;
WalkAndCreateNodes(peer);
BuildHandlers();
RefreshPathRegistrations();
// Emit cache updates
var childPeers = node.InvokeSync(() => peer.GetChildren());
for (var i = 0; i < childPeers.Count; i++)
{
var childNode = AtSpiNode.GetOrCreate(childPeers[i], this);
if (childNode is not null)
_cacheHandler.EmitAddAccessibleSignal(BuildCacheItem(childNode));
}
// Emit children-changed event
if (node.Handlers?.EventObjectHandler is { } eventHandler)
{
var reference = GetReference(node);
var childVariant = new DBusVariant(reference.ToDbusStruct());
eventHandler.EmitChildrenChangedSignal("add", 0, childVariant);
}
}
internal void EmitPropertyChange(AtSpiNode node, AutomationPropertyChangedEventArgs e)
{
if (_a11yConnection is null)
return;
if (node.Handlers?.EventObjectHandler is not { } eventHandler)
return;
if (e.Property == AutomationElementIdentifiers.NameProperty)
{
eventHandler.EmitPropertyChangeSignal(
"accessible-name",
new DBusVariant(e.NewValue?.ToString() ?? string.Empty));
}
else if (e.Property == AutomationElementIdentifiers.HelpTextProperty)
{
eventHandler.EmitPropertyChangeSignal(
"accessible-description",
new DBusVariant(e.NewValue?.ToString() ?? string.Empty));
}
}
internal void EmitFocusChange(AtSpiNode? focusedNode)
{
if (_a11yConnection is null || focusedNode is null)
return;
if (focusedNode.Handlers?.EventObjectHandler is { } eventHandler)
{
eventHandler.EmitStateChangedSignal("focused", 1, new DBusVariant(0));
}
}
private void WalkAndCreateNodes(AutomationPeer peer)
{
var node = AtSpiNode.GetOrCreate(peer, this);
if (node is not null)
RegisterNode(node);
var children = node?.InvokeSync(() => peer.GetChildren());
if (children is null)
return;
foreach (var child in children)
{
WalkAndCreateNodes(child);
}
}
private void BuildHandlers()
{
AtSpiNode[] snapshot;
lock (_treeGate)
{
snapshot = _nodesByPath.Values.ToArray();
}
foreach (var node in snapshot)
{
if (node.Handlers is not null)
continue;
var handlers = new AtSpiNodeHandlers(node);
var interfaces = node.InvokeSync(() => node.GetSupportedInterfaces());
if (interfaces.Contains(IfaceAccessible))
handlers.AccessibleHandler = new AtSpiAccessibleHandler(this, node);
if (interfaces.Contains(IfaceApplication))
handlers.ApplicationHandler = new AtSpiApplicationHandler(this, node);
if (interfaces.Contains(IfaceComponent))
handlers.ComponentHandler = new AtSpiComponentHandler(this, node);
if (interfaces.Contains(IfaceAction))
handlers.ActionHandler = new AtSpiActionHandler(this, node);
if (interfaces.Contains(IfaceValue))
handlers.ValueHandler = new AtSpiValueHandler(this, node);
handlers.EventObjectHandler = new AtSpiEventObjectHandler(this, node.Path);
node.Handlers = handlers;
}
_cacheHandler ??= new AtSpiCacheHandler(this);
}
private void RefreshPathRegistrations()
{
if (_a11yConnection is null)
return;
var desiredRegistrations = new Dictionary<string, (object Owner, Func<IDisposable> Register)>(StringComparer.Ordinal);
AtSpiNode[] snapshot;
lock (_treeGate)
{
snapshot = _nodesByPath.Values
.OrderBy(static n => n.Path, StringComparer.Ordinal)
.ToArray();
}
foreach (var node in snapshot)
{
if (node.Handlers is null)
continue;
var handlers = node.Handlers;
desiredRegistrations.Add(
node.Path,
(handlers, () => handlers.Register(_a11yConnection)));
}
if (_cacheHandler is not null)
{
var cacheHandler = _cacheHandler;
desiredRegistrations.Add(
CachePath,
(cacheHandler, () => _a11yConnection.RegisterObjects((DBusObjectPath)CachePath, new object[] { cacheHandler })));
}
// Remove stale registrations
foreach (var (path, active) in _pathRegistrations.ToArray())
{
if (!desiredRegistrations.TryGetValue(path, out var desired)
|| !ReferenceEquals(active.Owner, desired.Owner))
{
active.Registration.Dispose();
_pathRegistrations.Remove(path);
}
}
// Add new registrations
foreach (var (path, desired) in desiredRegistrations)
{
if (_pathRegistrations.ContainsKey(path))
continue;
var registration = desired.Register();
_pathRegistrations.Add(path, new ActivePathRegistration(desired.Owner, registration));
}
}
private async Task<string> GetAccessibilityBusAddressAsync()
{
try
{
await using var connection = await DBusConnection.ConnectSessionAsync();
var proxy = new OrgA11yBusProxy(connection, BusNameA11y, new DBusObjectPath(PathA11y));
return await proxy.GetAddressAsync();
}
catch
{
return string.Empty;
}
}
private async Task EmbedApplicationAsync()
{
if (_a11yConnection is null)
return;
var proxy = new OrgA11yAtspiSocketProxy(_a11yConnection, BusNameRegistry, new DBusObjectPath(RootPath));
await proxy.EmbedAsync(new AtSpiObjectReference(_uniqueName, new DBusObjectPath(RootPath)));
}
private void EmitInitialCacheSnapshot()
{
if (_cacheHandler is null)
return;
AtSpiNode[] snapshot;
lock (_treeGate)
{
snapshot = _nodesByPath.Values
.OrderBy(static node => node.Path, StringComparer.Ordinal)
.ToArray();
}
foreach (var node in snapshot)
{
_cacheHandler.EmitAddAccessibleSignal(BuildCacheItem(node));
}
}
private sealed class ActivePathRegistration
{
public ActivePathRegistration(object owner, IDisposable registration)
{
Owner = owner;
Registration = registration;
}
public object Owner { get; }
public IDisposable Registration { get; }
}
}
}

38
src/Avalonia.FreeDesktop.AtSpi/Avalonia.FreeDesktop.AtSpi.csproj

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(AvsCurrentTargetFramework);$(AvsLegacyTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<Import Project="../../build/NullableEnable.props" />
<Import Project="../../build/DevAnalyzers.props" />
<Import Project="../../build/TrimmingEnable.props" />
<Import Project="../../external/Avalonia.DBus/src/Avalonia.DBus.SourceGen/Avalonia.DBus.SourceGen.props" />
<ItemGroup>
<ProjectReference Include="../../packages/Avalonia/Avalonia.csproj" />
<ProjectReference Include="../../external/Avalonia.DBus/src/Avalonia.DBus/Avalonia.DBus.csproj" />
<ProjectReference Include="../../external/Avalonia.DBus/src/Avalonia.DBus.SourceGen/Avalonia.DBus.SourceGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="DBusXml/org.a11y.Bus.xml" DBusGeneratorMode="Proxy" />
<AdditionalFiles Include="DBusXml/Socket.xml" DBusGeneratorMode="Proxy" />
<AdditionalFiles Include="DBusXml/Accessible.xml" DBusGeneratorMode="Handler" />
<AdditionalFiles Include="DBusXml/Application.xml" DBusGeneratorMode="Handler" />
<AdditionalFiles Include="DBusXml/Component.xml" DBusGeneratorMode="Handler" />
<AdditionalFiles Include="DBusXml/Action.xml" DBusGeneratorMode="Handler" />
<AdditionalFiles Include="DBusXml/Value.xml" DBusGeneratorMode="Handler" />
<AdditionalFiles Include="DBusXml/Event.xml" DBusGeneratorMode="Handler" />
<AdditionalFiles Include="DBusXml/Cache.xml" DBusGeneratorMode="Handler" />
<AdditionalFiles Include="DBusXml/Image.xml" DBusGeneratorMode="Handler" />
<AdditionalFiles Include="DBusXml/DeviceEventController.xml" DBusGeneratorMode="Proxy" />
<AdditionalFiles Include="DBusXml/Registry.xml" DBusGeneratorMode="Proxy" />
<AdditionalFiles Include="DBusXml/Types.xml" />
</ItemGroup>
</Project>

883
src/Avalonia.FreeDesktop.AtSpi/DBusXml/Accessible.xml

@ -0,0 +1,883 @@
<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" xmlns:av="http://avaloniaui.net/dbus/1.0">
<av:ImportTypes>Types.xml</av:ImportTypes>
<!--
org.a11y.atspi.Accessible:
@short_description: Base interface which is implemented by all accessible objects.
-->
<interface name="org.a11y.atspi.Accessible">
<!--
Version: The version of this interface.
This property is incremented by one every time a new method, signal, or property
is added to this interface.
-->
<property name="version" type="u" access="read"/>
<!--
Name: Human-readable, localized, short name for the object.
Normally you need to set this for objects which do not have a labelled-by
relation. Consider a widget to select RGB colors by setting three sliders. The
names for the sliders would be "Red", "Green", "Blue", respectively, or
translations to application's locale. The names would be unnecessary if each
slider had a labelled-by relation to corresponding labels visible in the user
interface.
In general, something is missing from your application if an object that can be
interacted with has no Name or a labelled-by relation.
-->
<property name="Name" type="s" access="read"/>
<!--
Description: Human-readable, localized description of the object in more detail.
While the Name property is meant to be a short string that screen readers say
during normal navigation, the Description property is for when the user asks for
more detail.
-->
<property name="Description" type="s" access="read"/>
<!--
Parent: Accessible parent object of the current object.
The (so) is a string for the application name, and an object path.
Null parent: If the object has no parent (e.g. the application's root object is
being queried), return "" for the application name name and "/org/a11y/atspi/null"
for the object path.
Root object: An application must have a single root object, called
"/org/a11y/atspi/accessible/root". All other objects should have that one as
their highest-level ancestor.
-->
<property name="Parent" type="(so)" access="read">
<av:TypeDefinition>
<av:Struct Type="AtSpiObjectReference"/>
</av:TypeDefinition>
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QSpiObjectReference"/>
</property>
<!--
ChildCount: number of accessible children for this object.
-->
<property name="ChildCount" type="i" access="read"/>
<!--
Locale: Unix locale for the current object.
For an application, this may be the locale for the language that the application
shows in its user interface.
For a document being shown in an application, or a paragraph within a document,
the locale may refer to that object exclusively. For example, an application may
be showing itself in English ("en"), but it may be used to display a document in
Spanish ("es"). In the latter case, a screen reader will want to know that it
should switch to Spanish while reading the document.
-->
<property name="Locale" type="s" access="read"/>
<!--
AccessibleId: application-specific identifier for the current object.
This can be used to give a special id to an object for reliable identification by
ATs or for use in tests, for example, "my_widget". Note that there is no way to
directly find an object by its id; ATs may have to recursively get the children to
find a specific id. This is because accessible objects can be created dynamically,
and they do not always correspond to a static view of an application's data.
-->
<property name="AccessibleId" type="s" access="read"/>
<!--
HelpText: help text for the current object.
-->
<property name="HelpText" type="s" access="read"/>
<!--
GetChildAtIndex:
@index: 0-based index of the child to query.
Queries the Nth accessible child of the current object. It is expected that this
will correspond to the order that the GetChildren method would return.
Returns: The (so) is a string for the application name, and an object path.
Notes: implementations vary in their behavior when the index is out of range.
GTK4 returns an error, while atk-adaptor returns the null object path
"/org/a11y/atspi/null". To keep the type system gods happy, you should probably
return a DBus error in that case.
-->
<method name="GetChildAtIndex">
<arg direction="in" name="index" type="i"/>
<arg direction="out" type="(so)">
<av:TypeDefinition>
<av:Struct Type="AtSpiObjectReference"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
</method>
<!--
GetChildren:
Returns a list of the object's accessible children.
Each array element (so) is a string for the application name, and an object path.
An implementor may return an error if the object exposes a large
number of children such that returning a list would be prohibitive.
-->
<method name="GetChildren">
<arg direction="out" type="a(so)">
<av:TypeDefinition>
<av:List>
<av:Struct Type="AtSpiObjectReference"/>
</av:List>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiObjectReferenceArray"/>
</method>
<!--
GetIndexInParent:
Returns the 0-based index at which the object would be returned by calling
GetChildren on its parent, or -1 if the object has no containing
parent or on exception.
-->
<method name="GetIndexInParent">
<arg direction="out" type="i"/>
</method>
<!--
GetRelationSet:
Returns a set of relationships between the current object and others. Each
element in the outermost array contains a number that indicates the relation type
(see below), and an array of references to accessible objects to which that
relationship applies. Each element in the inner array is a (so) with a string for
the application name, and an object path.
Each relationship between objects (possibly one-to-many or many-to-one) allows
better semantic identification of how objects are associated with one another.
For instance, the ATSPI_RELATION_LABELLED_BY relationship may be used to identify
labelling information that should accompany the accessible name property when
presenting an object's content or identity to the end user. Similarly,
ATSPI_RELATION_CONTROLLER_FOR can be used to further specify the context in which
a valuator is useful, and/or the other UI components which are directly effected
by user interactions with the valuator. Common examples include association of
scrollbars with the viewport or panel which they control.
Relation types - these are the enum values from AtspiRelationType in atspi-constants.h:
0 - ATSPI_RELATION_NULL: Not a meaningful relationship; clients should not
normally encounter this value.
1 - ATSPI_RELATION_LABEL_FOR: Object is a label for one or more other objects.
2 - ATSPI_RELATION_LABELLED_BY: Object is labelled by one or more other
objects.
3 - ATSPI_RELATION_CONTROLLER_FOR: Object is an interactive object which
modifies the state, onscreen location, or other attributes of one or more
target objects.
4 - ATSPI_RELATION_CONTROLLED_BY: Object state, position, etc. is
modified/controlled by user interaction with one or more other objects.
For instance a viewport or scroll pane may be ATSPI_RELATION_CONTROLLED_BY
scrollbars.
5 - ATSPI_RELATION_MEMBER_OF: Object has a grouping relationship (e.g. 'same
group as') to one or more other objects.
6 - ATSPI_RELATION_TOOLTIP_FOR: Object is a tooltip associated with another
object.
7 - ATSPI_RELATION_NODE_CHILD_OF: Object is a child of the target.
8 - ATSPI_RELATION_NODE_PARENT_OF: Object is a parent of the target.
9 - ATSPI_RELATION_EXTENDED: Used to indicate that a relationship exists, but
its type is not specified in the enumeration.
10 - ATSPI_RELATION_FLOWS_TO: Object renders content which flows logically to
another object. For instance, text in a paragraph may flow to another
object which is not the 'next sibling' in the accessibility hierarchy.
11 - ATSPI_RELATION_FLOWS_FROM: Reciprocal of ATSPI_RELATION_FLOWS_TO.
12 - ATSPI_RELATION_SUBWINDOW_OF: Object is visually and semantically considered
a subwindow of another object, even though it is not the object's child.
Useful when dealing with embedded applications and other cases where the
widget hierarchy does not map cleanly to the onscreen presentation.
13 - ATSPI_RELATION_EMBEDS: Similar to ATSPI_RELATION_SUBWINDOW_OF, but
specifically used for cross-process embedding.
14 - ATSPI_RELATION_EMBEDDED_BY: Reciprocal of ATSPI_RELATION_EMBEDS. Used to
denote content rendered by embedded renderers that live in a separate process
space from the embedding context.
15 - ATSPI_RELATION_POPUP_FOR: Denotes that the object is a transient window or
frame associated with another onscreen object. Similar to ATSPI_TOOLTIP_FOR,
but more general. Useful for windows which are technically toplevels
but which, for one or more reasons, do not explicitly cause their
associated window to lose 'window focus'. Creation of an ATSPI_ROLE_WINDOW
object with the ATSPI_RELATION_POPUP_FOR relation usually requires
some presentation action on the part of assistive technology clients,
even though the previous toplevel ATSPI_ROLE_FRAME object may still be
the active window.
16 - ATSPI_RELATION_PARENT_WINDOW_OF: This is the reciprocal relation to
ATSPI_RELATION_POPUP_FOR.
17 - ATSPI_RELATION_DESCRIPTION_FOR: Reciprocal of ATSPI_RELATION_DESCRIBED_BY.
Indicates that this object provides descriptive information about the target
object(s). See also ATSPI_RELATION_DETAILS_FOR and ATSPI_RELATION_ERROR_FOR.
18 - ATSPI_RELATION_DESCRIBED_BY: Reciprocal of ATSPI_RELATION_DESCRIPTION_FOR.
Indicates that one or more target objects provide descriptive information
about this object. This relation type is most appropriate for information
that is not essential as its presentation may be user-configurable and/or
limited to an on-demand mechanism such as an assistive technology command.
For brief, essential information such as can be found in a widget's on-screen
label, use ATSPI_RELATION_LABELLED_BY. For an on-screen error message, use
ATSPI_RELATION_ERROR_MESSAGE. For lengthy extended descriptive information
contained in an on-screen object, consider using ATSPI_RELATION_DETAILS as
assistive technologies may provide a means for the user to navigate to
objects containing detailed descriptions so that their content can be more
closely reviewed.
19 - ATSPI_RELATION_DETAILS: Reciprocal of ATSPI_RELATION_DETAILS_FOR. Indicates
that this object has a detailed or extended description, the contents of
which can be found in the target object(s). This relation type is most
appropriate for information that is sufficiently lengthy as to make
navigation to the container of that information desirable. For less verbose
information suitable for announcement only, see ATSPI_RELATION_DESCRIBED_BY.
If the detailed information describes an error condition,
ATSPI_RELATION_ERROR_FOR should be used instead. Since 2.26.
20 - ATSPI_RELATION_DETAILS_FOR: Reciprocal of ATSPI_RELATION_DETAILS. Indicates
that this object provides a detailed or extended description about the target
object(s). See also ATSPI_RELATION_DESCRIPTION_FOR and ATSPI_RELATION_ERROR_FOR.
Since 2.26.
21 - ATSPI_RELATION_ERROR_MESSAGE: Reciprocal of ATSPI_RELATION_ERROR_FOR.
Indicates that this object has one or more errors, the nature of which is
described in the contents of the target object(s). Objects that have this
relation type should also contain ATSPI_STATE_INVALID_ENTRY when their
GetState method is called. Since: 2.26.
22 - ATSPI_RELATION_ERROR_FOR: Reciprocal of ATSPI_RELATION_ERROR_MESSAGE.
Indicates that this object contains an error message describing an invalid
condition in the target object(s). Since: 2.26.
-->
<method name="GetRelationSet">
<arg direction="out" type="a(ua(so))">
<av:TypeDefinition>
<av:List>
<av:Struct Type="AtSpiRelationEntry"/>
</av:List>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiRelationArray"/>
</method>
<!--
GetRole:
Gets the accessible role that the current object represents. Roles make it
possible for various UI toolkits to expose their controls to assistive
technologies (ATs) with a standard interface, regardless of toolkit. For example,
a widget that acts like a conventional push button (appears unpressed; presses
when acted upon; invokes a certain action when pressed) can expose an
ATSPI_ROLE_BUTTON role.
Role values - these are the enum values from AtspiRole in atspi-constants.h:
- 0 - ATSPI_ROLE_INVALID: A role indicating an error condition, such as
uninitialized Role data.
- 1 - ATSPI_ROLE_ACCELERATOR_LABEL: Object is a label indicating the keyboard
accelerators for the parent.
- 2 - ATSPI_ROLE_ALERT: Object is used to alert the user about something.
- 3 - ATSPI_ROLE_ANIMATION: Object contains a dynamic or moving image of some
kind.
- 4 - ATSPI_ROLE_ARROW: Object is a 2D directional indicator.
- 5 - ATSPI_ROLE_CALENDAR: Object contains one or more dates, usually arranged
into a 2D list.
- 6 - ATSPI_ROLE_CANVAS: Object that can be drawn into and is used to trap
events.
- 7 - ATSPI_ROLE_CHECK_BOX: A choice that can be checked or unchecked and
provides a separate indicator for the current state.
- 8 - ATSPI_ROLE_CHECK_MENU_ITEM: A menu item that behaves like a check box. See
ATSPI_ROLE_CHECK_BOX.
- 9 - ATSPI_ROLE_COLOR_CHOOSER: A specialized dialog that lets the user choose a
color.
- 10 - ATSPI_ROLE_COLUMN_HEADER: The header for a column of data.
- 11 - ATSPI_ROLE_COMBO_BOX: A list of choices the user can select from.
- 12 - ATSPI_ROLE_DATE_EDITOR: An object which allows entry of a date.
- 13 - ATSPI_ROLE_DESKTOP_ICON: An inconified internal frame within a DESKTOP_PANE.
- 14 - ATSPI_ROLE_DESKTOP_FRAME: A pane that supports internal frames and
iconified versions of those internal frames.
- 15 - ATSPI_ROLE_DIAL: An object that allows a value to be changed via rotating a
visual element, or which displays a value via such a rotating element.
- 16 - ATSPI_ROLE_DIALOG: A top level window with title bar and a border.
- 17 - ATSPI_ROLE_DIRECTORY_PANE: A pane that allows the user to navigate through
and select the contents of a directory.
- 18 - ATSPI_ROLE_DRAWING_AREA: An object used for drawing custom user interface
elements.
- 19 - ATSPI_ROLE_FILE_CHOOSER: A specialized dialog that displays the files in
the directory and lets the user select a file, browse a different
directory, or specify a filename.
- 20 - ATSPI_ROLE_FILLER: A object that fills up space in a user interface.
- 21 - ATSPI_ROLE_FOCUS_TRAVERSABLE: Don't use, reserved for future use.
- 22 - ATSPI_ROLE_FONT_CHOOSER: Allows selection of a display font.
- 23 - ATSPI_ROLE_FRAME: A top level window with a title bar, border, menubar,
etc.
- 24 - ATSPI_ROLE_GLASS_PANE: A pane that is guaranteed to be painted on top of
all panes beneath it.
- 25 - ATSPI_ROLE_HTML_CONTAINER: A document container for HTML, whose children
represent the document content.
- 26 - ATSPI_ROLE_ICON: A small fixed size picture, typically used to decorate
components.
- 27 - ATSPI_ROLE_IMAGE: An image, typically static.
- 28 - ATSPI_ROLE_INTERNAL_FRAME: A frame-like object that is clipped by a desktop
pane.
- 29 - ATSPI_ROLE_LABEL: An object used to present an icon or short string in an
interface.
- 30 - ATSPI_ROLE_LAYERED_PANE: A specialized pane that allows its children to be
drawn in layers, providing a form of stacking order.
- 31 - ATSPI_ROLE_LIST: An object that presents a list of objects to the user and
allows the user to select one or more of them.
- 32 - ATSPI_ROLE_LIST_ITEM: An object that represents an element of a list.
- 33 - ATSPI_ROLE_MENU: An object usually found inside a menu bar that contains a
list of actions the user can choose from.
- 34 - ATSPI_ROLE_MENU_BAR: An object usually drawn at the top of the primary
dialog box of an application that contains a list of menus the user can
x choose from.
- 35 - ATSPI_ROLE_MENU_ITEM: An object usually contained in a menu that presents
an action the user can choose.
- 36 - ATSPI_ROLE_OPTION_PANE: A specialized pane whose primary use is inside a
dialog.
- 37 - ATSPI_ROLE_PAGE_TAB: An object that is a child of a page tab list.
- 38 - ATSPI_ROLE_PAGE_TAB_LIST: An object that presents a series of panels (or
page tabs), one at a time,through some mechanism provided by the
object.
- 39 - ATSPI_ROLE_PANEL: A generic container that is often used to group objects.
- 40 - ATSPI_ROLE_PASSWORD_TEXT: A text object uses for passwords, or other places
where the text content is not shown visibly to the user.
- 41 - ATSPI_ROLE_POPUP_MENU: A temporary window that is usually used to offer the
user a list of choices, and then hides when the user selects one of those
choices.
- 42 - ATSPI_ROLE_PROGRESS_BAR: An object used to indicate how much of a task has
been completed.
- 43 - ATSPI_ROLE_BUTTON: An object the user can manipulate to tell the
application to do something.
- 44 - ATSPI_ROLE_RADIO_BUTTON: A specialized check box that will cause other
radio buttons in the same group to become unchecked when this one is
checked.
- 45 - ATSPI_ROLE_RADIO_MENU_ITEM: Object is both a menu item and a "radio button".
See ATSPI_ROLE_RADIO_BUTTON.
- 46 - ATSPI_ROLE_ROOT_PANE: A specialized pane that has a glass pane and a
layered pane as its children.
- 47 - ATSPI_ROLE_ROW_HEADER: The header for a row of data.
- 48 - ATSPI_ROLE_SCROLL_BAR: An object usually used to allow a user to
incrementally view a large amount of data by moving the bounds of a
viewport along a one-dimensional axis.
- 49 - ATSPI_ROLE_SCROLL_PANE: An object that allows a user to incrementally view
a large amount of information. Scroll pane objects are usually
accompanied by ATSPI_ROLE_SCROLL_BAR controllers, on which the
ATSPI_RELATION_CONTROLLER_FOR and ATSPI_RELATION_CONTROLLED_BY
reciprocal relations are set. See the GetRelationSet method.
- 50 - ATSPI_ROLE_SEPARATOR: An object usually contained in a menu to provide a
visible and logical separation of the contents in a menu.
- 51 - ATSPI_ROLE_SLIDER: An object that allows the user to select from a bounded range.
Unlike ATSPI_ROLE_SCROLL_BAR, ATSPI_ROLE_SLIDER objects need not control
'viewport'-like objects.
- 52 - ATSPI_ROLE_SPIN_BUTTON: An object which allows one of a set of choices to
be selected, and which displays the current choice.
- 53 - ATSPI_ROLE_SPLIT_PANE: A specialized panel that presents two other panels
at the same time.
- 54 - ATSPI_ROLE_STATUS_BAR: Object displays non-quantitative status information
(c.f. ATSPI_ROLE_PROGRESS_BAR)
- 55 - ATSPI_ROLE_TABLE: An object used to represent information in terms of rows
and columns.
- 56 - ATSPI_ROLE_TABLE_CELL: A 'cell' or discrete child within a Table. Note:
Table cells need not have ATSPI_ROLE_TABLE_CELL, other
role values are valid as well.
- 57 - ATSPI_ROLE_TABLE_COLUMN_HEADER: An object which labels a particular column
in a Table interface interface.
- 58 - ATSPI_ROLE_TABLE_ROW_HEADER: An object which labels a particular row in a
Table interface. Table rows and columns may also be labelled via the
ATSPI_RELATION_LABEL_FOR/ATSPI_RELATION_LABELLED_BY relationships; see
the GetRelationSet method.
- 59 - ATSPI_ROLE_TEAROFF_MENU_ITEM: Object allows menu to be removed from menubar
and shown in its own window.
- 60 - ATSPI_ROLE_TERMINAL: An object that emulates a terminal.
- 61 - ATSPI_ROLE_TEXT: An interactive widget that supports multiple lines of text
and optionally accepts user input, but whose purpose is not to solicit user
input. Thus ATSPI_ROLE_TEXT is appropriate for the text view in a plain text
editor but inappropriate for an input field in a dialog box or web form. For
widgets whose purpose is to solicit input from the user, see ATSPI_ROLE_ENTRY
and ATSPI_ROLE_PASSWORD_TEXT. For generic objects which display a brief amount
of textual information, see ATSPI_ROLE_STATIC.
- 62 - ATSPI_ROLE_TOGGLE_BUTTON: A specialized push button that can be checked or
unchecked, but does not procide a separate indicator for the current
state.
- 63 - ATSPI_ROLE_TOOL_BAR: A bar or palette usually composed of push buttons or
toggle buttons.
- 64 - ATSPI_ROLE_TOOL_TIP: An object that provides information about another
object.
- 65 - ATSPI_ROLE_TREE: An object used to repsent hierarchical information to the
user.
- 66 - ATSPI_ROLE_TREE_TABLE: An object that presents both tabular and
hierarchical info to the user.
- 67 - ATSPI_ROLE_UNKNOWN: The object contains some accessible information,
but its role is not known.
- 68 - ATSPI_ROLE_VIEWPORT: An object usually used in a scroll pane, or to
otherwise clip a larger object or content renderer to a specific
onscreen viewport.
- 69 - ATSPI_ROLE_WINDOW: A top level window with no title or border.
- 70 - ATSPI_ROLE_EXTENDED: means that the role for this item is known, but not
included in the core enumeration. Deprecated since 2.24.
- 71 - ATSPI_ROLE_HEADER: An object that serves as a document header.
- 72 - ATSPI_ROLE_FOOTER: An object that serves as a document footer.
- 73 - ATSPI_ROLE_PARAGRAPH: An object which is contains a single paragraph of
text content. See also ATSPI_ROLE_TEXT.
- 74 - ATSPI_ROLE_RULER: An object which describes margins and tab stops, etc.
for text objects which it controls (should have
ATSPI_RELATION_CONTROLLER_FOR relation to such).
- 75 - ATSPI_ROLE_APPLICATION: An object corresponding to the toplevel accessible
of an application, which may contain ATSPI_ROLE_FRAME objects or other
accessible objects. Children of objects with the ATSPI_ROLE_DESKTOP_FRAME role
are generally ATSPI_ROLE_APPLICATION objects.
- 76 - ATSPI_ROLE_AUTOCOMPLETE: The object is a dialog or list containing items
for insertion into an entry widget, for instance a list of words for
completion of a text entry.
- 77 - ATSPI_ROLE_EDITBAR: The object is an editable text object in a toolbar.
- 78 - ATSPI_ROLE_EMBEDDED: The object is an embedded component container. This
role is a "grouping" hint that the contained objects share a context
which is different from the container in which this accessible is
embedded. In particular, it is used for some kinds of document embedding,
and for embedding of out-of-process component, "panel applets", etc.
- 79 - ATSPI_ROLE_ENTRY: The object is a component whose textual content may be
entered or modified by the user, provided ATSPI_STATE_EDITABLE is present.
A readonly ATSPI_ROLE_ENTRY object (i.e. where ATSPI_STATE_EDITABLE is
not present) implies a read-only 'text field' in a form, as opposed to a
title, label, or caption.
- 80 - ATSPI_ROLE_CHART: The object is a graphical depiction of quantitative data.
It may contain multiple subelements whose attributes and/or description
may be queried to obtain both the quantitative data and information about
how the data is being presented. The ATSPI_LABELLED_BY relation is
particularly important in interpreting objects of this type, as is the
accessible description property. See ATSPI_ROLE_CAPTION.
- 81 - ATSPI_ROLE_CAPTION: The object contains descriptive information, usually
textual, about another user interface element such as a table, chart, or
image.
- 82 - ATSPI_ROLE_DOCUMENT_FRAME: The object is a visual frame or container which
contains a view of document content. Document frames may occur within
another Document instance, in which case the second document may be
said to be embedded in the containing instance. HTML frames are often
ATSPI_ROLE_DOCUMENT_FRAME: Either this object, or a singleton descendant,
should implement the org.a11y.atspi.Document interface.
- 83 - ATSPI_ROLE_HEADING: The object serves as a heading for content which
follows it in a document. The 'heading level' of the heading, if
available, may be obtained by querying the object's attributes.
- 84 - ATSPI_ROLE_PAGE: The object is a containing instance which encapsulates a
page of information. ATSPI_ROLE_PAGE is used in documents and content which
support a paginated navigation model.
- 85 - ATSPI_ROLE_SECTION: The object is a containing instance of document content
which constitutes a particular 'logical' section of the document. The
type of content within a section, and the nature of the section division
itself, may be obtained by querying the object's attributes. Sections
may be nested.
- 86 - ATSPI_ROLE_REDUNDANT_OBJECT: The object is redundant with another object in
the hierarchy, and is exposed for purely technical reasons. Objects of
this role should be ignored by clients, if they are encountered at all.
- 87 - ATSPI_ROLE_FORM: The object is a containing instance of document content
which has within it components with which the user can interact in order
to input information; i.e. the object is a container for pushbuttons,
comboboxes, text input fields, and other 'GUI' components. ATSPI_ROLE_FORM
should not, in general, be used for toplevel GUI containers or dialogs,
but should be reserved for 'GUI' containers which occur within document
content, for instance within Web documents, presentations, or text
documents. Unlike other GUI containers and dialogs which occur inside
application instances, ATSPI_ROLE_FORM containers' components are
associated with the current document, rather than the current foreground
application or viewer instance.
- 88 - ATSPI_ROLE_LINK: The object is a hypertext anchor, i.e. a "link" in a
hypertext document. Such objects are distinct from 'inline' content
which may also use the Hypertext/Hyperlink interfaces to indicate
the range/location within a text object where an inline or embedded object
lies.
- 89 - ATSPI_ROLE_INPUT_METHOD_WINDOW: The object is a window or similar viewport
which is used to allow composition or input of a 'complex character',
in other words it is an "input method window".
- 90 - ATSPI_ROLE_TABLE_ROW: A row in a table.
- 91 - ATSPI_ROLE_TREE_ITEM: An object that represents an element of a tree.
- 92 - ATSPI_ROLE_DOCUMENT_SPREADSHEET: A document frame which contains a
spreadsheet.
- 93 - ATSPI_ROLE_DOCUMENT_PRESENTATION: A document frame which contains a
presentation or slide content.
- 94 - ATSPI_ROLE_DOCUMENT_TEXT: A document frame which contains textual content,
such as found in a word processing application.
- 95 - ATSPI_ROLE_DOCUMENT_WEB: A document frame which contains HTML or other
markup suitable for display in a web browser.
- 96 - ATSPI_ROLE_DOCUMENT_EMAIL: A document frame which contains email content
to be displayed or composed either in plain text or HTML.
- 97 - ATSPI_ROLE_COMMENT: An object found within a document and designed to
present a comment, note, or other annotation. In some cases, this object
might not be visible until activated.
- 98 - ATSPI_ROLE_LIST_BOX: A non-collapsible list of choices the user can select from.
- 99 - ATSPI_ROLE_GROUPING: A group of related widgets. This group typically has a label.
- 100 - ATSPI_ROLE_IMAGE_MAP: An image map object. Usually a graphic with multiple
hotspots, where each hotspot can be activated resulting in the loading of
another document or section of a document.
- 101 - ATSPI_ROLE_NOTIFICATION: A transitory object designed to present a
message to the user, typically at the desktop level rather than inside a
particular application.
- 102 - ATSPI_ROLE_INFO_BAR: An object designed to present a message to the user
within an existing window.
- 103 - ATSPI_ROLE_LEVEL_BAR: A bar that serves as a level indicator to, for
instance, show the strength of a password or the state of a battery. Since: 2.8
- 104 - ATSPI_ROLE_TITLE_BAR: A bar that serves as the title of a window or a
dialog. Since: 2.12
- 105 - ATSPI_ROLE_BLOCK_QUOTE: An object which contains a text section
that is quoted from another source. Since: 2.12
- 106 - ATSPI_ROLE_AUDIO: An object which represents an audio
element. Since: 2.12
- 107 - ATSPI_ROLE_VIDEO: An object which represents a video
element. Since: 2.12
- 108 - ATSPI_ROLE_DEFINITION: A definition of a term or concept. Since: 2.12
- 109 - ATSPI_ROLE_ARTICLE: A section of a page that consists of a
composition that forms an independent part of a document, page, or
site. Examples: A blog entry, a news story, a forum post. Since: 2.12
- 110 - ATSPI_ROLE_LANDMARK: A region of a web page intended as a
navigational landmark. This is designed to allow Assistive
Technologies to provide quick navigation among key regions within a
document. Since: 2.12
- 111 - ATSPI_ROLE_LOG: A text widget or container holding log content, such
as chat history and error logs. In this role there is a
relationship between the arrival of new items in the log and the
reading order. The log contains a meaningful sequence and new
information is added only to the end of the log, not at arbitrary
points. Since: 2.12
- 112 - ATSPI_ROLE_MARQUEE: A container where non-essential information
changes frequently. Common usages of marquee include stock tickers
and ad banners. The primary difference between a marquee and a log
is that logs usually have a meaningful order or sequence of
important content changes. Since: 2.12
- 113 - ATSPI_ROLE_MATH: A text widget or container that holds a mathematical
expression. Since: 2.12
- 114 - ATSPI_ROLE_RATING: A widget whose purpose is to display a rating,
such as the number of stars associated with a song in a media
player. Objects of this role should also implement
the Value interface. Since: 2.12
- 115 - ATSPI_ROLE_TIMER: An object containing a numerical counter which
indicates an amount of elapsed time from a start point, or the time
remaining until an end point. Since: 2.12
- 116 - ATSPI_ROLE_STATIC: A generic non-container object whose purpose is to display
a brief amount of information to the user and whose role is known by the
implementor but lacks semantic value for the user. Examples in which
ATSPI_ROLE_STATIC is appropriate include the message displayed in a message
box and an image used as an alternative means to display text.
ATSPI_ROLE_STATIC should not be applied to widgets which are traditionally
interactive, objects which display a significant amount of content, or any
object which has an accessible relation pointing to another object. The
displayed information, as a general rule, should be exposed through the
accessible name of the object. For labels which describe another widget, see
ATSPI_ROLE_LABEL. For text views, see ATSPI_ROLE_TEXT. For generic
containers, see ATSPI_ROLE_PANEL. For objects whose role is not known by the
implementor, see ATSPI_ROLE_UNKNOWN. Since: 2.16.
- 117 - ATSPI_ROLE_MATH_FRACTION: An object that represents a mathematical fraction. Since: 2.16.
- 118 - ATSPI_ROLE_MATH_ROOT: An object that represents a mathematical expression
displayed with a radical. Since: 2.16.
- 119 - ATSPI_ROLE_SUBSCRIPT: An object that contains text that is displayed as a
subscript. Since: 2.16.
- 120 - ATSPI_ROLE_SUPERSCRIPT: An object that contains text that is displayed as a
superscript. Since: 2.16.
- 121 - ATSPI_ROLE_DESCRIPTION_LIST: An object that represents a list of term-value
groups. A term-value group represents an individual description and consist
of one or more names (ATSPI_ROLE_DESCRIPTION_TERM) followed by one or more
values (ATSPI_ROLE_DESCRIPTION_VALUE). For each list, there should not be
more than one group with the same term name. Since: 2.26.
- 122 - ATSPI_ROLE_DESCRIPTION_TERM: An object that represents a term or phrase
with a corresponding definition. Since: 2.26.
- 123 - ATSPI_ROLE_DESCRIPTION_VALUE: An object that represents the description,
definition, or value of a term. Since: 2.26.
- 124 - ATSPI_ROLE_FOOTNOTE: An object that contains the text of a footnote. Since: 2.26.
- 125 - ATSPI_ROLE_CONTENT_DELETION: Content previously deleted or proposed to be
deleted, e.g. in revision history or a content view providing suggestions
from reviewers. Since: 2.34.
- 126 - ATSPI_ROLE_CONTENT_INSERTION: Content previously inserted or proposed to be
inserted, e.g. in revision history or a content view providing suggestions
from reviewers. Since: 2.34.
- 127 - ATSPI_ROLE_MARK: A run of content that is marked or highlighted, such as for
reference purposes, or to call it out as having a special purpose. If the
marked content has an associated section in the document elaborating on the
reason for the mark, then an ATSPI_RELATION_DETAILS relation should be used on the mark
to point to that associated section. In addition, the reciprocal relation
ATSPI_RELATION_DETAILS_FOR should be used on the associated content section
to point back to the mark. See the GetRelationSet method. Since: 2.36.
- 128 - ATSPI_ROLE_SUGGESTION: A container for content that is called out as a
proposed change from the current version of the document, such as by a reviewer of
the content. An object with this role should include children with
ATSPI_ROLE_CONTENT_DELETION and/or ATSPI_ROLE_CONTENT_INSERTION, in any order, to
indicate what the actual change is. Since: 2.36
- 129 - ATSPI_ROLE_PUSH_BUTTON_MENU: A specialized push button to open a menu. Since: 2.46
- 130 - ATSPI_ROLE_SWITCH: A switch that can be toggled on/off. Since: 2.56
-->
<method name="GetRole">
<arg direction="out" type="u"/>
</method>
<!--
GetRoleName:
Gets a UTF-8 string corresponding to the name of the role played by an object.
This method will return useful values for roles that fall outside the
enumeration used in the GetRole method. Implementing this method is
optional, and it may be removed in a future version of the API.
Libatspi will only call id in the event of an unknown role.
-->
<method name="GetRoleName">
<arg direction="out" type="s"/>
</method>
<!--
GetLocalizedRoleName:
Gets a UTF-8 string corresponding to the name of the role played by an object, translated
to the current locale.
This method will return useful values for roles that fall outside the
enumeration used in the GetRole method. Implementing this method is
optional, and it may be removed in a future version of the API.
Libatspi will only call id in the event of an unknown role.
-->
<method name="GetLocalizedRoleName">
<arg direction="out" type="s"/>
</method>
<!--
GetState:
Gets the set of states currently held by an object.
Elements in the array are enumeration values from AtspiStateType in atspi-constants.h:
- 0 - ATSPI_STATE_INVALID: Indicates an invalid state - probably an error
condition.
- 1 - ATSPI_STATE_ACTIVE: Indicates a window is currently the active window, or
an object is the active subelement within a container or table. ATSPI_STATE_ACTIVE
should not be used for objects which have ATSPI_STATE_FOCUSABLE or
ATSPI_STATE_SELECTABLE: Those objects should use
ATSPI_STATE_FOCUSED and ATSPI_STATE_SELECTED respectively.
ATSPI_STATE_ACTIVE is a means to indicate that an object which is not
focusable and not selectable is the currently-active item within its
parent container.
- 2 - ATSPI_STATE_ARMED: Indicates that the object is armed.
- 3 - ATSPI_STATE_BUSY: Indicates the current object is busy, i.e. onscreen
representation is in the process of changing, or the object is
temporarily unavailable for interaction due to activity already in progress.
- 4 - ATSPI_STATE_CHECKED: Indicates this object is currently checked.
- 5 - ATSPI_STATE_COLLAPSED: Indicates this object is collapsed.
- 6 - ATSPI_STATE_DEFUNCT: Indicates that this object no longer has a valid
backing widget (for instance, if its peer object has been destroyed).
- 7 - ATSPI_STATE_EDITABLE: Indicates the user can change the contents of this
object.
- 8 - ATSPI_STATE_ENABLED: Indicates that this object is enabled, i.e. that it
currently reflects some application state. Objects that are "greyed out"
may lack this state, and may lack the ATSPI_STATE_SENSITIVE if direct
user interaction cannot cause them to acquire ATSPI_STATE_ENABLED.
See ATSPI_STATE_SENSITIVE.
- 9 - ATSPI_STATE_EXPANDABLE: Indicates this object allows progressive
disclosure of its children.
- 10 - ATSPI_STATE_EXPANDED: Indicates this object is expanded.
- 11 - ATSPI_STATE_FOCUSABLE: Indicates this object can accept keyboard focus,
which means all events resulting from typing on the keyboard will
normally be passed to it when it has focus.
- 12 - ATSPI_STATE_FOCUSED: Indicates this object currently has the keyboard
focus.
- 13 - ATSPI_STATE_HAS_TOOLTIP: Indicates that the object has an associated
tooltip.
- 14 - ATSPI_STATE_HORIZONTAL: Indicates the orientation of this object is
horizontal.
- 15 - ATSPI_STATE_ICONIFIED: Indicates this object is minimized and is
represented only by an icon.
- 16 - ATSPI_STATE_MODAL: Indicates something must be done with this object
before the user can interact with an object in a different window.
- 17 - ATSPI_STATE_MULTI_LINE: Indicates this (text) object can contain multiple
lines of text.
- 18 - ATSPI_STATE_MULTISELECTABLE: Indicates this object allows more than one of
its children to be selected at the same time, or in the case of text
objects, that the object supports non-contiguous text selections.
- 19 - ATSPI_STATE_OPAQUE: Indicates this object paints every pixel within its
rectangular region. It also indicates an alpha value of unity, if it
supports alpha blending.
- 20 - ATSPI_STATE_PRESSED: Indicates this object is currently pressed.
- 21 - ATSPI_STATE_RESIZABLE: Indicates the size of this object's size is not
fixed.
- 22 - ATSPI_STATE_SELECTABLE: Indicates this object is the child of an object
that allows its children to be selected and that this child is one of
those children that can be selected.
- 23 - ATSPI_STATE_SELECTED: Indicates this object is the child of an object that
allows its children to be selected and that this child is one of those
children that has been selected.
- 24 - ATSPI_STATE_SENSITIVE: Indicates this object is sensitive, e.g. to user
interaction. ATSPI_STATE_SENSITIVE usually accompanies.
ATSPI_STATE_ENABLED for user-actionable controls, but may be found in the
absence of ATSPI_STATE_ENABLED if the current visible state of the control
is "disconnected" from the application state. In such cases, direct user
interaction can often result in the object gaining ATSPI_STATE_SENSITIVE,
for instance if a user makes an explicit selection using an object whose
current state is ambiguous or undefined. See ATSPI_STATE_ENABLED,
ATSPI_STATE_INDETERMINATE.
- 25 - ATSPI_STATE_SHOWING: Indicates this object, the object's parent, the
object's parent's parent, and so on, are all 'shown' to the end-user,
i.e. subject to "exposure" if blocking or obscuring objects do not
interpose between this object and the top of the window stack.
- 26 - ATSPI_STATE_SINGLE_LINE: Indicates this (text) object can contain only a
single line of text.
- 27 - ATSPI_STATE_STALE: Indicates that the information returned for this object
may no longer be synchronized with the application state. This can occur
if the object has ATSPI_STATE_TRANSIENT, and can also occur towards the
end of the object peer's lifecycle.
- 28 - ATSPI_STATE_TRANSIENT: Indicates this object is transient.
- 29 - ATSPI_STATE_VERTICAL: Indicates the orientation of this object is vertical;
for example this state may appear on such objects as scrollbars, text
objects (with vertical text flow), separators, etc.
- 30 - ATSPI_STATE_VISIBLE: Indicates this object is visible, e.g. has been
explicitly marked for exposure to the user. ATSPI_STATE_VISIBLE is no
guarantee that the object is actually unobscured on the screen, only that
it is 'potentially' visible, barring obstruction, being scrolled or clipped
out of the field of view, or having an ancestor container that has not yet
made visible. A widget is potentially onscreen if it has both
ATSPI_STATE_VISIBLE and ATSPI_STATE_SHOWING. The absence of
ATSPI_STATE_VISIBLE and ATSPI_STATE_SHOWING is
semantically equivalent to saying that an object is 'hidden'.
- 31 - ATSPI_STATE_MANAGES_DESCENDANTS: Indicates that "active-descendant-changed"
event is sent when children become 'active' (i.e. are selected or
navigated to onscreen). Used to prevent need to enumerate all children
in very large containers, like tables. The presence of
ATSPI_STATE_MANAGES_DESCENDANTS is an indication to the client that the
children should not, and need not, be enumerated by the client.
Objects implementing this state are expected to provide relevant state
notifications to listening clients, for instance notifications of
visibility changes and activation of their contained child objects, without
the client having previously requested references to those children.
- 32 - ATSPI_STATE_INDETERMINATE: Indicates that a check box or other boolean
indicator is in a state other than checked or not checked. This
usually means that the boolean value reflected or controlled by the
object does not apply consistently to the entire current context.
For example, a checkbox for the "Bold" attribute of text may have
ATSPI_STATE_INDETERMINATE if the currently selected text contains a mixture
of weight attributes. In many cases interacting with a
ATSPI_STATE_INDETERMINATE object will cause the context's corresponding
boolean attribute to be homogenized, whereupon the object will lose
ATSPI_STATE_INDETERMINATE and a corresponding state-changed event will be
fired.
- 33 - ATSPI_STATE_REQUIRED: Indicates that user interaction with this object is
'required' from the user, for instance before completing the
processing of a form.
- 34 - ATSPI_STATE_TRUNCATED: Indicates that an object's onscreen content
is truncated, e.g. a text value in a spreadsheet cell.
- 35 - ATSPI_STATE_ANIMATED: Indicates this object's visual representation is
dynamic, not static. This state may be applied to an object during an
animated 'effect' and be removed from the object once its visual
representation becomes static. Some applications, notably content viewers,
may not be able to detect all kinds of animated content. Therefore the
absence of this state should not be taken as
definitive evidence that the object's visual representation is
static; this state is advisory.
- 36 - ATSPI_STATE_INVALID_ENTRY: This object has indicated an error condition
due to failure of input validation. For instance, a form control may
acquire this state in response to invalid or malformed user input.
- 37 - ATSPI_STATE_SUPPORTS_AUTOCOMPLETION: This state indicates that the object
in question implements some form of typeahead or
pre-selection behavior whereby entering the first character of one or more
sub-elements causes those elements to scroll into view or become
selected. Subsequent character input may narrow the selection further as
long as one or more sub-elements match the string. This state is normally
only useful and encountered on objects that implement AtspiSelection.
In some cases the typeahead behavior may result in full or partial
completion of the data in the input field, in which case
these input events may trigger text-changed events from the source.
- 38 - ATSPI_STATE_SELECTABLE_TEXT: This state indicates that the object in
question supports text selection. It should only be exposed on objects
which implement the AtspiText interface, in order to distinguish this state
from ATSPI_STATE_SELECTABLE, which infers that the object in question is a
selectable child of an object which implements AtspiSelection. While
similar, text selection and subelement selection are distinct operations.
- 39 - ATSPI_STATE_IS_DEFAULT: This state indicates that the object in question is
the 'default' interaction object in a dialog, i.e. the one that gets
activated if the user presses "Enter" when the dialog is initially
posted.
- 40 - ATSPI_STATE_VISITED: This state indicates that the object (typically a
hyperlink) has already been activated or invoked, with the result that
some backing data has been downloaded or rendered.
- 41 - ATSPI_STATE_CHECKABLE: Indicates this object has the potential to
be checked, such as a checkbox or toggle-able table cell. Since: 2.12
- 42 - ATSPI_STATE_HAS_POPUP: Indicates that the object has a popup
context menu or sub-level menu which may or may not be
showing. This means that activation renders conditional content.
Note that ordinary tooltips are not considered popups in this
context. Since: 2.12
- 43 - ATSPI_STATE_READ_ONLY: Indicates that an object which is ENABLED and
SENSITIVE has a value which can be read, but not modified, by the
user. Since: 2.16
-->
<method name="GetState">
<arg direction="out" type="au">
<av:TypeDefinition>
<av:List/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiIntList"/>
</method>
<!--
GetAttributes:
Gets a list of name/value pairs of attributes or annotations for this object. For
typographic, textual, or textually-semantic attributes, see the Text.GetAttributes
method.
FIXME: is there a list of well-known attributes?
-->
<method name="GetAttributes">
<arg direction="out" type="a{ss}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiAttributeSet"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiAttributeSet"/>
</method>
<!--
GetApplication:
Returns a string for the application name, and an object path for the containing
application object.
-->
<method name="GetApplication">
<arg direction="out" type="(so)">
<av:TypeDefinition>
<av:Struct Type="AtSpiObjectReference"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
</method>
<!--
GetInterfaces:
Returns an array of accessible interface names supported by the current object.
-->
<method name="GetInterfaces">
<arg direction="out" type="as"/>
</method>
</interface>
</node>

145
src/Avalonia.FreeDesktop.AtSpi/DBusXml/Action.xml

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" xmlns:av="http://avaloniaui.net/dbus/1.0">
<av:ImportTypes>Types.xml</av:ImportTypes>
<!--
org.a11y.atspi.Action:
@short_description: Allows exploring and invoking the actions of a user-actionable UI component.
For example, a button may expose a "click" action; a popup menu may expose an "open"
action. Components which are not "passive" providers of UI information should
implement this interface, unless there is a more specialized interface for
interaction like org.a11y.atspi.Text or org.a11y.atspi.Value.
-->
<interface name="org.a11y.atspi.Action">
<!--
Version: The version of this interface.
This property is incremented by one every time a new method, signal, or property
is added to this interface.
-->
<property name="version" type="u" access="read"/>
<!--
NActions: returns the number of available actions.
By convention, if there is more than one action available, the first one is
considered the "default" action of the object.
-->
<property name="NActions" type="i" access="read"/>
<!--
GetDescription:
@index: 0-based index of the action to query.
Returns: The localized description for the action at the specified @index. For
example, a screen reader will read out this description when the user asks for
extra detail on an action. For example, "Clicks the button" for the "click"
action of a button.
-->
<method name="GetDescription">
<arg type="i" name="index" direction="in"/>
<arg type="s" direction="out"/>
</method>
<!--
GetName:
@index: 0-based index of the action to query.
Returns: Machine-readable name for the action at the specified @index.
-->
<method name="GetName">
<arg type="i" name="index" direction="in"/>
<arg type="s" direction="out"/>
</method>
<!--
GetLocalizedName:
@index: 0-based index of the action to query.
Returns: A short, localized name for the action at the specified @index. This is
what screen readers will read out during normal navigation. For example, "Click"
for a button.
-->
<method name="GetLocalizedName">
<arg type="i" name="index" direction="in"/>
<arg type="s" direction="out"/>
</method>
<!--
GetKeyBinding:
@index: 0-based index of the action to query.
Gets the keybinding which can be used to activate this action, if one
exists. The string returned should contain localized, human-readable,
key sequences as they would appear when displayed on screen. It must
be in the format "mnemonic;sequence;shortcut".
- The mnemonic key activates the object if it is presently enabled onscreen.
This typically corresponds to the underlined letter within the widget.
Example: "n" in a traditional "New..." menu item or the "a" in "Apply" for
a button.
- The sequence is the full list of keys which invoke the action even if the
relevant element is not currently shown on screen. For instance, for a menu
item the sequence is the keybindings used to open the parent menus before
invoking. The sequence string is colon-delimited. Example: "Alt+F:N" in a
traditional "New..." menu item.
- The shortcut, if it exists, will invoke the same action without showing
the component or its enclosing menus or dialogs. Example: "Ctrl+N" in a
traditional "New..." menu item.
Example: For a traditional "New..." menu item, the expected return value
would be: "N;Alt+F:N;Ctrl+N" for the English locale and "N;Alt+D:N;Strg+N"
for the German locale. If, hypothetically, this menu item lacked a mnemonic,
it would be represented by ";;Ctrl+N" and ";;Strg+N" respectively.
If there is no key binding for this action, return "".
-->
<method name="GetKeyBinding">
<arg type="i" name="index" direction="in"/>
<arg type="s" direction="out"/>
</method>
<!--
GetActions:
Returns: an array of (localized_name, localized description, keybinding) for the
actions that an object supports. See the GetKeyBinding method for a description
of that field's syntax.
This is equivalent to using the methods GetLocalizedName, GetDescription,
GetKeyBinding for each action, but with a single call and thus less DBus traffic.
By convention, if there is more than one action available, the first one is
considered the "default" action of the object.
-->
<method name="GetActions">
<arg direction="out" type="a(sss)">
<av:TypeDefinition>
<av:List>
<av:Struct Type="AtSpiAction"/>
</av:List>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiActionArray"/>
</method>
<!--
DoAction:
@index: 0-based index of the action to perform.
Performs the specified action on the object.
Returns: true on success, false otherwise.
-->
<method name="DoAction">
<arg direction="in" name="index" type="i"/>
<arg direction="out" type="b"/>
</method>
</interface>
</node>

86
src/Avalonia.FreeDesktop.AtSpi/DBusXml/Application.xml

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" xmlns:av="http://avaloniaui.net/dbus/1.0">
<av:ImportTypes>Types.xml</av:ImportTypes>
<!--
org.a11y.atspi.Application:
@short_description: Interface that must be implemented by the root object of an application.
-->
<interface name="org.a11y.atspi.Application">
<!--
ToolkitName: name of the toolkit used to implement the application's user interface.
-->
<property name="ToolkitName" type="s" access="read"/>
<!--
Version: version of the toolkit used to implement the application's user interface.
Deprecated: Use org.a11y.atspi.Application:ToolkitVersion instead.
-->
<property name="Version" type="s" access="read">
<annotation name="org.freedesktop.DBus.Deprecated" value="true"/>
</property>
<!--
Version: version of the toolkit used to implement the application's user interface.
-->
<property name="ToolkitVersion" type="s" access="read"/>
<!--
AtspiVersion: You should return "2.1" here.
This was intended to be the version of the atspi interfaces
that the application supports, but atspi will probably move to
using versioned interface names instead. Just return "2.1" here.
-->
<property name="AtspiVersion" type="s" access="read"/>
<!--
InterfaceVersion: The version of this interface.
This property is incremented by one every time a new method, signal, or property
is added to this interface.
-->
<property name="InterfaceVersion" type="u" access="read"/>
<!--
Id: set to an arbitrary numerical id when an application registers with the registry.
When a freshly-started application uses the
org.a11y.atspi.Socket.Embed method to register with the
accessibility registry, the registry will set a numerical id
on the application.
Per https://gitlab.gnome.org/GNOME/at-spi2-core/-/issues/82 it
may turn out that this id is not actually used subsequently.
This is a remnant of the time when registryd actually had to
make up identifiers for each application. With DBus, however,
it is the bus that assigns unique names to applications that
connect to it.
Your application or toolkit can remember the Id passed when
the accessibility registry sets this property, and return it
back when the property is read.
-->
<property name="Id" type="i" access="readwrite"/>
<!-- This method is not used.
See https://gitlab.gnome.org/GNOME/orca/-/issues/260
-->
<method name="GetLocale">
<arg direction="in" name="lctype" type="u"/>
<arg direction="out" type="s"/>
</method>
<!--
Get a P2P DBus address for further direct communication from the client.
This allows for communication without being proxied by the daemon.
-->
<method name="GetApplicationBusAddress">
<arg direction="out" type="s"/>
</method>
</interface>
</node>

119
src/Avalonia.FreeDesktop.AtSpi/DBusXml/Cache.xml

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" xmlns:av="http://avaloniaui.net/dbus/1.0">
<av:ImportTypes>Types.xml</av:ImportTypes>
<!--
org.a11y.atspi.Cache:
@short_description: Interface to query accessible objects in bulk.
The application should expose this interface at the /org/a11y/atspi/cache object
path.
The org.a11y.atspi.Accessible interface has methods like GetChildren and
GetChildAtIndex, but these only transfer an object's DBus id. The caller has to
then query the object's properties individually. Transferring objects one by one and
then their properties produces a lot of traffic in the accessibility bus.
So, this Cache interface can be used to query objects in bulk. Assistive tech
should try to do a bulk query of all the objects in a new window with the GetItems
method, and then update them dynamically from the AddAccessible and RemoveAccessible
signals.
FIXME: Does GetItems only get called if an application implements
GetApplicationBusAddress? GTK4 doesn't implement that, but it implements GetItems -
does that ever get called?
-->
<interface name="org.a11y.atspi.Cache">
<!--
Version: The version of this interface.
This property is incremented by one every time a new method, signal, or property
is added to this interface.
-->
<property name="version" type="u" access="read"/>
<!--
GetItems:
Bulk query an application's accessible objects.
Returns: an array with one element for each available object. Each element's
fields are like this:
- (so): accessible object reference - DBus name and object
path. The rest of the fields refer to this main object.
- (so): application reference - DBus name and object path. This is the owner of
the main object; the root path of the application that registered via
the Embed method of the org.a11y.atspi.Socket interface.
- (so): parent object reference - DBus name and object path.
If the main object has no parent:
- If it is a control, or a window, return the parent application.
- If the object has the application role, return a null reference. FIXME:
atk-adaptor/adaptors/cache-adaptor.c:append_cache_item() returns a
reference to the registry in this case (the one it obtained from the
initial Socket::Embed call); GTK4 returns a null reference.
- Otherwise, return a null reference ("" for the application name name and
"/org/a11y/atspi/null" for the object path).
- i: index in parent, or -1 for transient widgets/menu items. Equivalent to the
GetIndexInParent method of the org.a11y.atspi.Accessible interface.
- i: child count of main object, or -1 for defunct/menus. Equivalent to the
ChildCount property of the org.a11y.atspi.Accessible interface.
- as: array of names of the interfaces that the main object supports. Equivalent
to the GetInterfaces method of the org.a11y.atspi.Accessible interface.
- s: human-readable, localized, short name for the main object. Equivalent to the
Name property of the org.a11y.atspi.Accessible interface.
- u: role. Equivalent to the GetRole method of the org.a11y.atspi.Accessible interface.
- s: human-readable, localized description of the object in more detail.
Equivalent to the Description property of the org.a11y.atspi.Accessible interface.
- au: Set of states currently held by an object. Equivalent to the GetState
method of the org.a11y.atspi.Accessible interface.
Deprecation note: The signature for the return value of this method changed in
2015, in commit b2c8c4c7. It used to be "a((so)(so)(so)a(so)assusau)". The
"a(so)" instead of "ii" is a list of references to child objects. The
implementation in atspi-misc.c can handle either version, although the intention
is to deprecate the code that handles the old version. Qt still uses this old
signature and should be changed to the new scheme (see qspi_struct_marshallers.cpp
in the Qt source code).
-->
<method name="GetItems">
<arg direction="out" name="nodes" type="a((so)(so)(so)iiassusau)">
<av:TypeDefinition>
<av:List>
<av:Struct Type="AtSpiAccessibleCacheItem"/>
</av:List>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiAccessibleCacheArray"/>
</method>
<!--
AddAccessible: to be emitted when a new object is added.
See the GetItems method for a description of the signature.
-->
<signal name="AddAccessible">
<arg name="nodeAdded" type="((so)(so)(so)iiassusau)">
<av:TypeDefinition>
<av:Struct Type="AtSpiAccessibleCacheItem"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiAccessibleCacheItem"/>
</signal>
<!--
RemoveAccessible: to be emitted when an object is no longer available.
@nodeRemoved: (so) string for the application name and object path.
-->
<signal name="RemoveAccessible">
<arg name="nodeRemoved" type="(so)">
<av:TypeDefinition>
<av:Struct Type="AtSpiObjectReference"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/>
</signal>
</interface>
</node>

346
src/Avalonia.FreeDesktop.AtSpi/DBusXml/Component.xml

@ -0,0 +1,346 @@
<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" xmlns:av="http://avaloniaui.net/dbus/1.0">
<av:ImportTypes>Types.xml</av:ImportTypes>
<!--
org.a11y.atspi.Component:
@short_description: Interface for GUI components like widgets or other visible elements.
-->
<interface name="org.a11y.atspi.Component">
<!--
Version: The version of this interface.
This property is incremented by one every time a new method, signal, or property
is added to this interface.
-->
<property name="version" type="u" access="read"/>
<!--
Contains:
@x: X coordinate of point.
@y: Y coordinate of point.
@coord_type: Whether the coordinates are relative to the screen or to the
component's top level window; see the description.
Queries whether a point (x, y) is inside the component.
The @coord_type values are as follows, and correspond to AtkCoordType:
- 0 - Coordinates are relative to the screen.
- 1 - Coordinates are relative to the component's toplevel window.
- 2 - Coordinates are relative to the component's immediate parent.
-->
<method name="Contains">
<arg direction="in" name="x" type="i"/>
<arg direction="in" name="y" type="i"/>
<arg direction="in" name="coord_type" type="u"/>
<arg direction="out" type="b"/>
</method>
<!--
GetAccessibleAtPoint:
@x: X coordinate of point.
@y: Y coordinate of point.
@coord_type: Whether the coordinates are relative to the screen or to the
component's top level window; see the description.
Gets a reference to the accessible object that contains an (x, y) pair of
coordinates.
The @coord_type values are as follows, and correspond to AtkCoordType:
- 0 - Coordinates are relative to the screen.
- 1 - Coordinates are relative to the component's toplevel window.
- 2 - Coordinates are relative to the component's immediate parent.
Returns: A DBus name and object reference (so) for the sought object, or a null
object reference "/org/a11y/atspi/null" if there is no object at the specified
coordinates.
-->
<method name="GetAccessibleAtPoint">
<arg direction="in" name="x" type="i"/>
<arg direction="in" name="y" type="i"/>
<arg direction="in" name="coord_type" type="u"/>
<arg direction="out" type="(so)">
<av:TypeDefinition>
<av:Struct Type="AtSpiObjectReference"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
</method>
<!--
GetExtents:
@coord_type: Whether the coordinates are relative to the screen or to the
component's top level window; see the description.
Queries the pixel extents of a component.
The @coord_type values are as follows, and correspond to AtkCoordType:
- 0 - Coordinates are relative to the screen.
- 1 - Coordinates are relative to the component's toplevel window.
- 2 - Coordinates are relative to the component's immediate parent.
Returns: a tuple (x, y, width, height) corresponding to the rectangle for the
component's extents.
-->
<method name="GetExtents">
<arg direction="in" name="coord_type" type="u"/>
<arg direction="out" type="(iiii)">
<av:TypeDefinition>
<av:Struct Type="AtSpiRect"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiRect"/>
</method>
<!--
GetPosition:
@coord_type: Whether the coordinates are relative to the screen or to the
component's top level window; see the description.
Queries the upper-left position of a component.
The @coord_type values are as follows, and correspond to AtkCoordType:
- 0 - Coordinates are relative to the screen.
- 1 - Coordinates are relative to the component's toplevel window.
- 2 - Coordinates are relative to the component's immediate parent.
Returns: (x, y) coordinates of the component's upper-left corner.
-->
<method name="GetPosition">
<arg direction="in" name="coord_type" type="u"/>
<arg direction="out" name="x" type="i"/>
<arg direction="out" name="y" type="i"/>
</method>
<!--
GetSize:
Queries the pixel size of a component.
Returns: (width, height) of the component's rectangular area.
-->
<method name="GetSize">
<arg direction="out" name="width" type="i"/>
<arg direction="out" name="height" type="i"/>
</method>
<!--
GetLayer:
Queries the UI layer at which a component is rendered, which can help in
determining when components occlude one another.
The layer of a component indicates its relative stacking order with respect to the
onscreen visual representation of the UI. The layer index, in combination
with the component's extents, can be used to compute the visibility of
all or part of a component. This is important in programmatic determination of
region-of-interest for magnification, and in flat screen review models of the
screen, as well as for other uses. Objects residing in two of the
Layer categories support further z-ordering information, with
respect to their peers in the same layer: namely, WINDOW and
MDI. Relative stacking order for other objects within the same layer
is not available; the recommended heuristic is first child paints first. In other
words, assume that the first siblings in the child list are subject to being
overpainted by later siblings if their bounds intersect. The order of layers, from
bottom to top, is as follows:
- 0 - INVALID: Error condition.
- 1 - BACKGROUND: Reserved for the desktop background; this is the bottom-most
layer, over which everything else is painted.
- 2 - CANVAS: The 'background' layer for most content renderers and UI component containers.
- 3 - WIDGET: The layer in which the majority of ordinary 'foreground' widgets reside.
- 4 - MDI: A special layer between CANVAS and WIDGET, in which the 'pseudo-windows'
(e.g. the Multiple-Document Interface frames) reside. See the GetMDIZOrder
method.
- 5 - POPUP: Layer for popup window content, above WIDGET.
- 6 - OVERLAY: The topmost layer.
- 7 - WINDOW: The layer in which a toplevel window background usually resides.
-->
<method name="GetLayer">
<arg direction="out" type="u"/>
</method>
<!--
GetMDIZOrder:
Queries the Z stacking order of a component which is in the MDI or WINDOW layer,
per the GetLayer method. Bigger z-order numbers are nearer the top.
Returns: The z order of the component, or -1 if it is not in the MDI layer.
-->
<method name="GetMDIZOrder">
<arg direction="out" type="n"/>
</method>
<!--
GrabFocus:
Attempts to set the keyboard input focus to the component.
Returns: true if successful, or false otherwise.
-->
<method name="GrabFocus">
<arg direction="out" type="b"/>
</method>
<!--
GetAlpha:
Gets the opacity/alpha value of a component, if alpha blending is in use.
Returns: opacity value in the [0.0, 1.0] range. 0 is fully transparent and 1 is fully opaque.
-->
<method name="GetAlpha">
<arg direction="out" type="d"/>
</method>
<!--
SetExtents:
@x: the new horizontal position to which the component should be moved.
@y: the new vertical position to which the component should be moved.
@width: the width to which the component should be resized.
@height: the height to which the component should be resized.
@coord_type: Whether the coordinates are relative to the screen or to the
component's top level window; see the description.
Moves and resizes the component.
The @coord_type values are as follows, and correspond to AtspiCoordType:
- 0 - Coordinates are relative to the screen.
- 1 - Coordinates are relative to the component's toplevel window.
- 2 - Coordinates are relative to the component's immediate parent.
Returns: true if successful, or false otherwise.
-->
<method name="SetExtents">
<arg direction="in" name="x" type="i"/>
<arg direction="in" name="y" type="i"/>
<arg direction="in" name="width" type="i"/>
<arg direction="in" name="height" type="i"/>
<arg direction="in" name="coord_type" type="u"/>
<arg direction="out" type="b"/>
</method>
<!--
SetPosition:
@x: the new horizontal position to which the component should be moved.
@y: the new vertical position to which the component should be moved.
@coord_type: Whether the coordinates are relative to the screen or to the
component's top level window; see the description.
Moves the component to the specified position.
The @coord_type values are as follows, and correspond to AtkCoordType:
0 - Coordinates are relative to the screen.
1 - Coordinates are relative to the component's toplevel window.
2 - Coordinates are relative to the component's immediate parent.
Returns: true if successful, or false otherwise.
-->
<method name="SetPosition">
<arg direction="in" name="x" type="i"/>
<arg direction="in" name="y" type="i"/>
<arg direction="in" name="coord_type" type="u"/>
<arg direction="out" type="b"/>
</method>
<!--
SetSize:
@width: the width to which the component should be resized.
@height: the height to which the component should be resized.
Resizes the component to the given pixel dimensions.
Returns: true if successful, or false otherwise.
-->
<method name="SetSize">
<arg direction="in" name="width" type="i"/>
<arg direction="in" name="height" type="i"/>
<arg direction="out" type="b"/>
</method>
<!--
ScrollTo:
@type: How to position the component within its parent; see the description.
Makes the component visible on the screen by scrolling all necessary parents.
The @type corresponds to AtkScrollType:
- 0 - TOP_LEFT: Scroll the object vertically and horizontally to bring
its top left corner to the top left corner of the window.
- 1 - BOTTOM_RIGHT: Scroll the object vertically and horizontally to
bring its bottom right corner to the bottom right corner of the window.
- 2 - TOP_EDGE: Scroll the object vertically to bring its top edge to
the top edge of the window.
- 3 - BOTTOM_EDGE: Scroll the object vertically to bring its bottom
edge to the bottom edge of the window.
- 4 - LEFT_EDGE: Scroll the object vertically and horizontally to bring
its left edge to the left edge of the window.
- 5 - RIGHT_EDGE: Scroll the object vertically and horizontally to
bring its right edge to the right edge of the window.
- 6 - ANYWHERE: Scroll the object vertically and horizontally so that
as much as possible of the object becomes visible. The exact placement is
determined by the application.
Returns: true if successful, or false otherwise.
-->
<method name="ScrollTo">
<arg direction="in" name="type" type="u"/>
<arg direction="out" type="b"/>
</method>
<!--
ScrollToPoint:
@coord_type: Whether the coordinates are relative to the screen or to the
component's top level window; see the description.
@x: X coordinate within the component to make visible.
@y: Y coordinate within the component to make visible.
Similar to the ScrollTo method, but makes a specific point from the component
visible in its parent.
The @coord_type values are as follows, and correspond to AtkCoordType:
- 0 - Coordinates are relative to the screen.
- 1 - Coordinates are relative to the component's toplevel window.
- 2 - Coordinates are relative to the component's immediate parent.
Returns: true if successful, or false otherwise.
-->
<method name="ScrollToPoint">
<arg direction="in" name="coord_type" type="u"/>
<arg direction="in" name="x" type="i"/>
<arg direction="in" name="y" type="i"/>
<arg direction="out" type="b"/>
</method>
</interface>
</node>

148
src/Avalonia.FreeDesktop.AtSpi/DBusXml/DeviceEventController.xml

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" xmlns:av="http://avaloniaui.net/dbus/1.0">
<av:ImportTypes>Types.xml</av:ImportTypes>
<!--
org.a11y.atspi.DeviceEventController:
@short_description: Legacy interface for keystroke listeners and generation of keyboard/mouse events
This interface is being replaced by the functions in atspi-device-listener.h.
-->
<interface name="org.a11y.atspi.DeviceEventController">
<!--
Version: The version of this interface.
This property is incremented by one every time a new method, signal, or property
is added to this interface.
-->
<property name="version" type="u" access="read"/>
<!--
RegisterKeystrokeListener:
@listener: path of object to be notified when the following keys are pressed
@keys: array of (key_code, key_sym, key_string, unused)
@mask: modifier mask in X11 style (see Xlib.h)
@types: mask of press/release; see the description below.
@mode: struct of flags (synchronous, preemptive, global), see the description below.
The @types can be a mask of the following:
- KEY_PRESS = 1 << 0
- KEY_RELEASE = 1 << 1
Note that Orca always passes (KEY_PRESS | KEY_RELEASE).
The @mode is composed of three flags (see AtspiKeyListenerSyncType):
- synchronous: Events are delivered synchronously, before
the currently focused application sees them. If false,
events may be delivered asynchronously, which means in some
cases they may already have been delivered to the
application before the AT client receives the notification.
- preemptive: (called CANCONSUME in AtspiKeyListenerSyncType)
Events may be consumed by the AT client. Requires the synchronous flag to be set.
- global: (called ALL_WINDOWS in AtspiKeyListenerSyncType)
Events are received not from the application toolkit layer,
but from the device driver or windowing system subsystem.
Returns: boolean indicating whether the operation was successful. This is always
TRUE for non-global listeners (c.f. @mode), and may be FALSE for global listeners
if the underlying XGrabKey() failed (see spi_dec_x11_grab_key).
-->
<method name="RegisterKeystrokeListener">
<arg direction="in" name="listener" type="o"/>
<arg direction="in" name="keys" type="a(iisi)">
<av:TypeDefinition>
<av:List>
<av:Struct Type="AtSpiKeyDefinition"/>
</av:List>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QSpiKeyTypeArray"/>
<arg direction="in" name="mask" type="u">
<av:TypeDefinition>
<av:BitFlags Type="AtSpiModifierMask"/>
</av:TypeDefinition>
</arg>
<arg direction="in" name="types" type="u">
<av:TypeDefinition>
<av:BitFlags Type="AtSpiEventTypeMask"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In3" value="QSpiEventTypeArray"/>
<arg direction="in" name="mode" type="(bbb)">
<av:TypeDefinition>
<av:Struct Type="AtSpiEventMode"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In4" value="QSpiEventMode"/>
<arg direction="out" type="b"/>
</method>
<method name="DeregisterKeystrokeListener">
<arg direction="in" name="listener" type="o"/>
<arg direction="in" name="keys" type="a(iisi)">
<av:TypeDefinition>
<av:List>
<av:Struct Type="AtSpiKeyDefinition"/>
</av:List>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QSpiKeyTypeArray"/>
<arg direction="in" name="mask" type="u">
<av:TypeDefinition>
<av:BitFlags Type="AtSpiModifierMask"/>
</av:TypeDefinition>
</arg>
<arg direction="in" name="type" type="u">
<av:TypeDefinition>
<av:BitFlags Type="AtSpiEventTypeMask"/>
</av:TypeDefinition>
</arg>
</method>
<method name="GetKeystrokeListeners">
<arg direction="out" type="a(souua(iisi)u(bbb))">
<av:TypeDefinition>
<av:List>
<av:Struct Type="AtSpiKeystrokeListener"/>
</av:List>
</av:TypeDefinition>
</arg>
</method>
<method name="GenerateKeyboardEvent">
<arg direction="in" name="keycode" type="i"/>
<arg direction="in" name="keystring" type="s"/>
<arg direction="in" name="type" type="u"/>
</method>
<method name="GenerateMouseEvent">
<arg direction="in" name="x" type="i"/>
<arg direction="in" name="y" type="i"/>
<arg direction="in" name="eventName" type="s"/>
</method>
<method name="NotifyListenersSync">
<arg direction="in" name="event" type="(uiuuisb)">
<av:TypeDefinition>
<av:Struct Type="AtSpiDeviceEvent"/>
</av:TypeDefinition>
</arg>
<arg direction="out" type="b"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiDeviceEvent"/>
</method>
<method name="NotifyListenersAsync">
<arg direction="in" name="event" type="(uiuuisb)">
<av:TypeDefinition>
<av:Struct Type="AtSpiDeviceEvent"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiDeviceEvent"/>
</method>
</interface>
</node>

799
src/Avalonia.FreeDesktop.AtSpi/DBusXml/Event.xml

@ -0,0 +1,799 @@
<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" xmlns:av="http://avaloniaui.net/dbus/1.0">
<av:ImportTypes>Types.xml</av:ImportTypes>
<!--
These interfaces allow applications to send events to notify ATs of
changes that they should be aware of. For historical reasons, all
events include a string, two integers, and a variant. The meaning
of these values will depend on the specific event, and they may be
unused, in which case an empty string or a value of 0 should be
sent. In addition, a dictionary of strings/variants should be
sent. This is intended to allow applications to proactively send
additional properties that an AT might be interested in. This is
reserved for future use. Currently, this dictionary should be empty.
TODO: Need documentation for the signals themselves.
-->
<interface name="org.a11y.atspi.Event.Object">
<!--
Version: The version of this interface.
This property is incremented by one every time a new method, signal, or property
is added to this interface.
-->
<property name="version" type="u" access="read"/>
<signal name="PropertyChange">
<arg name="property" type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg name="value" type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="BoundsChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v" name="bounds"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="LinkSelected">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="StateChanged">
<arg name="state" type="s"/>
<arg name="enabled" type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="ChildrenChanged">
<arg name="operation" type="s"/>
<arg name="index_in_parent" type="i"/>
<arg type="i"/>
<arg name="child" type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="VisibleDataChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="SelectionChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="ModelChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="ActiveDescendantChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg name="child" type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Announcement">
<arg type="s"/>
<!-- politeness should be 1 (ATSPI_LIVE_POLITE) or 2
(ATSPI_LIVE_ASSERTIVE) -->
<arg type="i" name="politeness"/>
<arg type="i"/>
<!-- The variant should enclose a string with the text to be announced -->
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="AttributesChanged">
<!-- name specifies the name of the attribute that has changed, when available -->
<arg name="name" type="s"/>
<arg type="i"/>
<arg type="i"/>
<!-- The variant should include a string with the attribute's new value, when available -->
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<!-- Table events. TODO: move to Table interface? -->
<signal name="RowInserted">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="RowReordered">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="RowDeleted">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="ColumnInserted">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="ColumnReordered">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="ColumnDeleted">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<!-- Text events. TODO: move to Text interface? -->
<signal name="TextBoundsChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="TextSelectionChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="TextChanged">
<arg name="detail" type="s"/>
<arg name="start_pos" type="i"/>
<arg name="length" type="i"/>
<arg name="text" type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="TextAttributesChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="TextCaretMoved">
<arg type="s"/>
<arg name="position" type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
</interface>
<interface name="org.a11y.atspi.Event.Window">
<signal name="PropertyChange">
<arg name="property" type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Minimize">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Maximize">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Restore">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Close">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Create">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Reparent">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="DesktopCreate">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="DesktopDestroy">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Destroy">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Activate">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Deactivate">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Raise">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Lower">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Move">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Resize">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Shade">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="uUshade">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Restyle">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
</interface>
<interface name="org.a11y.atspi.Event.Mouse">
<signal name="Abs">
<arg type="s"/>
<arg name="x" type="i"/>
<arg name="y" type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Rel">
<arg type="s"/>
<arg name="x" type="i"/>
<arg name="y" type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Button">
<arg name="detail" type="s"/>
<arg name="mouse_x" type="i"/>
<arg name="mouse_y" type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
</interface>
<interface name="org.a11y.atspi.Event.Keyboard">
<signal name="Modifiers">
<arg type="s"/>
<arg name="previous_modifiers" type="i"/>
<arg name="current_modifiers" type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
</interface>
<interface name="org.a11y.atspi.Event.Terminal">
<signal name="LineChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="ColumncountChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="LinecountChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="ApplicationChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="CharwidthChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
</interface>
<interface name="org.a11y.atspi.Event.Document">
<signal name="LoadComplete">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="Reload">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="LoadStopped">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="ContentChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="AttributesChanged">
<!-- name specifies the name of the attribute that has changed, when available -->
<arg name="name" type="s"/>
<arg type="i"/>
<arg type="i"/>
<!-- The variant should include a string with the attribute's new value, when available -->
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
<signal name="PageChanged">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
</interface>
<interface name="org.a11y.atspi.Event.Focus">
<!-- Focus is deprecated in favor of StateChanged with focus passed as its first argument -->
<signal name="Focus">
<arg type="s"/>
<arg type="i"/>
<arg type="i"/>
<arg type="v"/>
<arg name="properties" type="a{sv}">
<av:TypeDefinition>
<av:Dictionary Type="AtSpiEventProperties"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
</signal>
</interface>
</node>

87
src/Avalonia.FreeDesktop.AtSpi/DBusXml/Image.xml

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" xmlns:av="http://avaloniaui.net/dbus/1.0">
<av:ImportTypes>Types.xml</av:ImportTypes>
<!--
org.a11y.atspi.Image:
@short_description: Interface for accessible objects that represent
graphical images.
-->
<interface name="org.a11y.atspi.Image">
<!--
Version: The version of this interface.
This property is incremented by one every time a new method, signal, or property
is added to this interface.
-->
<property name="version" type="u" access="read"/>
<!--
ImageDescription: the description of the image.
-->
<property name="ImageDescription" type="s" access="read"/>
<!--
ImageLocale: a POSIX LC_MESSAGES-style locale value for the image
description and text.
-->
<property name="ImageLocale" type="s" access="read"/>
<!--
GetImageExtents:
@coordType: Specifies the reference point for the coordinates to
be returned. 0 = relative to the screen, 1 = relative to the
application's top-level window, 2 = relative to the accessible's
parent. Requesting coordinates relative to the screen is deprecated
and may not be supported by the application.
Gets the bounding box of the image displayed in a
specified Image implementor.
The returned values are meaningful only if the Image has both
STATE_VISIBLE and STATE_SHOWING.
-->
<method name="GetImageExtents">
<arg direction="in" name="coordType" type="u"/>
<arg direction="out" type="(iiii)">
<av:TypeDefinition>
<av:Struct Type="AtSpiRect"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiRect"/>
</method>
<!--
GetImagePosition:
@coordType: Specifies the reference point for the coordinates to
be returned. 0 = relative to the screen, 1 = relative to the
application's top-level window, 2 = relative to the accessible's
parent. Requesting coordinates relative to the screen is deprecated
and may not be supported by the application.
Gets the minimum x and y coordinates of the image displayed in a
specified Image implementor.
The returned values are meaningful only if the Image has both
STATE_VISIBLE and STATE_SHOWING.
-->
<method name="GetImagePosition">
<arg direction="in" name="coordType" type="u"/>
<arg direction="out" name="x" type="i"/>
<arg direction="out" name="y" type="i"/>
</method>
<!--
GetImageSize:
Gets the size of the image displayed in a specified object.
The returned values are meaningful only if the Image has both
STATE_VISIBLE and STATE_SHOWING.
-->
<method name="GetImageSize">
<arg direction="out" name="width" type="i"/>
<arg direction="out" name="height" type="i"/>
</method>
</interface>
</node>

131
src/Avalonia.FreeDesktop.AtSpi/DBusXml/Registry.xml

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" xmlns:av="http://avaloniaui.net/dbus/1.0">
<av:ImportTypes>Types.xml</av:ImportTypes>
<!--
org.a11y.atspi.Registry:
@short_description: Interface implemented by the AT-SPI registry daemon.
The registry daemon listens on org.a11y.atspi.Registry and exposes an
object at /org/a11y/atspi/registry that implements this interface.
-->
<interface name="org.a11y.atspi.Registry">
<!--
Version: The version of this interface.
This property is incremented by one every time a new method, signal, or property
is added to this interface.
-->
<property name="version" type="u" access="read"/>
<!--
RegisterEvent:
@event_type: a character string indicating the type of events for which
notification is requested. Format is
EventClass:major_type:minor_type:detail
where all subfields other than EventClass are optional.
EventClasses include "object", "window", "mouse",
and toolkit events (e.g. "Gtk", "AWT").
Examples: "focus:", "Gtk:GtkWidget:button_press_event".
@properties: A list of properties that applications should pass
when sending the event specified in @event_type. This is intended
to allow an AT to proactively request the information that it is
interested in and minimize the need to make additional queries in
response to the event. This is currently unimplemented; the
specification would need to be fleshed out here.
@app_bus_name: The application that this request pertains to.
This allows an app to indicate that it is interested in a
particular event only for a certain application. If this string is
empty, then this registration applies to all applications.
This method is to be called by an AT to indicate that it is
interested in receiving a particular event from applications.
Applications can optionally call GetRegisteredEvents and listen for
EventListenerRewgistered and EventListenerDeregistered signals and,
in this way, only send events that an AT is interested in receiving.
-->
<method name="RegisterEvent">
<arg direction="in" name="event" type="s"/>
<arg direction="in" name="properties" type="as"/>
<arg direction="in" name="app_bus_name" type="s"/>
</method>
<!--
DeregisterEvent:
@event_type: A character string indicating the event to be
deregistered. See RegisterEvent for a description of the format of
this string.
Indicates that an AT is no longer interested in listening to a
particular event.
TODO: Add app_bus_name here.
-->
<method name="DeregisterEvent">
<arg direction="in" name="event" type="s"/>
</method>
<!--
GetRegisteredEvents:
@events: A dictionary listing the registered events. For each entry,
the first string gives the bus name of the AT holding the
registration. The second string gives the event. See RegisterEvent
for a description of the format of this string.
-->
<method name="GetRegisteredEvents">
<arg direction="out" name="events" type="a(ss)">
<av:TypeDefinition>
<av:List>
<av:Struct Type="AtSpiEventListener"/>
</av:List>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiEventListenerArray"/>
</method>
<!--
EventListenerRegistered:
@bus: The bus name of the AT registering the event.
@event: The event being registered. See RegisterEvent for a
description of the format of this string.
@properties: An array of properties that the AT is interested in
receiving along with the event. Currently unimplemented; the
specification for this would need to be fleshed out.
Indicates that an AT has requested to listen for an event.
Applications can optionally listen to this signal to maintain a
list of events that ATs are interested in receiving and only send
events when needed.
-->
<signal name="EventListenerRegistered">
<arg name="bus" type="s"/>
<arg name="event" type="s"/>
<arg name="properties" type="as"/>
</signal>
<!--
EventListenerDeregistered:
@bus: The bus name of the AT registering the event.
@event: The event being deregistered. See RegisterEvent for a
description of the format of this string.
Indicates that an AT is no longer interested in listening for an
event. Applications can optionally listen to this signal to maintain
a list of events that ATs are interested in receiving and only send
events when needed.
-->
<signal name="EventListenerDeregistered">
<arg name="bus" type="s"/>
<arg name="event" type="s"/>
</signal>
</interface>
</node>

102
src/Avalonia.FreeDesktop.AtSpi/DBusXml/Socket.xml

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" xmlns:av="http://avaloniaui.net/dbus/1.0">
<av:ImportTypes>Types.xml</av:ImportTypes>
<!--
org.a11y.atspi.Socket:
@short_description: Interface to register an application on the registry.
-->
<interface name="org.a11y.atspi.Socket">
<!--
Version: The version of this interface.
This property is incremented by one every time a new method, signal, or property
is added to this interface.
-->
<property name="version" type="u" access="read"/>
<!--
Embed:
@plug: a string for the unique bus name of the application, and an object path
for the application's' root object.
This is the entry point for an application that wants to register itself against
the accessibility registry. The application's root object, which it passes in
@plug, must support the org.a11y.atspi.Application interface.
When an application calls this method on the registry, the following handshake happens:
* Application calls this method on the registry to identify itself.
* The registry sets the "Id" property on the org.a11y.atspi.Application interface on the @plug object.
* The Embed method returns with the bus name and object path for the registry's root object.
Returns: the bus name and object path of the registry's root object.
-->
<method name="Embed">
<arg direction="in" name="plug" type="(so)">
<av:TypeDefinition>
<av:Struct Type="AtSpiObjectReference"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/>
<arg direction="out" name="socket" type="(so)">
<av:TypeDefinition>
<av:Struct Type="AtSpiObjectReference"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
</method>
<!--
Embedded:
@path: the object path of the socket.
This method is called by a socket to inform the plug that it is being
embedded. The plug should register the embedding socket as its parent.
-->
<method name="Embedded">
<arg direction="in" name="path" type="s"/>
</method>
<!--
Unembed:
@plug: a string for the unique bus name of the application, and an object path
for the application's' root object.
Unregisters an application from the accesibility registry. It is not necessary to
call this method; the accessibility registry detects when an application
disconnects from the bus.
-->
<method name="Unembed">
<arg direction="in" name="plug" type="(so)">
<av:TypeDefinition>
<av:Struct Type="AtSpiObjectReference"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/>
</method>
<!--
Available:
@socket: application and object path for the registry's root object.
The accessibility registry emits this signal early during startup, when it has
registered with the DBus daemon and is available for calls from applications.
-->
<signal name="Available">
<arg name="socket" type="(so)">
<av:TypeDefinition>
<av:Struct Type="AtSpiObjectReference"/>
</av:TypeDefinition>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/>
</signal>
</interface>
</node>

158
src/Avalonia.FreeDesktop.AtSpi/DBusXml/Types.xml

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8"?>
<Types xmlns="http://avaloniaui.net/dbus/1.0">
<Struct Name="AtSpiObjectReference">
<Property Name="Service"/>
<Property Name="Path"/>
</Struct>
<Struct Name="AtSpiAction">
<Property Name="LocalizedName"/>
<Property Name="LocalizedDescription"/>
<Property Name="KeyBinding"/>
</Struct>
<Struct Name="AtSpiAccessibleCacheItem">
<Property Name="Path" Type="AtSpiObjectReference"/>
<Property Name="Application" Type="AtSpiObjectReference"/>
<Property Name="Parent" Type="AtSpiObjectReference"/>
<Property Name="IndexInParent"/>
<Property Name="ChildCount"/>
<Property Name="SupportedInterfaces">
<List/>
</Property>
<Property Name="Name"/>
<Property Name="Role"/>
<Property Name="Description"/>
<Property Name="State">
<List/>
</Property>
</Struct>
<Struct Name="AtSpiEventListener">
<Property Name="ListenerAddress"/>
<Property Name="EventName"/>
</Struct>
<Struct Name="AtSpiRelationEntry">
<Property Name="RelationType"/>
<Property Name="Targets">
<List>
<Struct Type="AtSpiObjectReference"/>
</List>
</Property>
</Struct>
<Struct Name="AtSpiTextRange">
<Property Name="StartOffset"/>
<Property Name="EndOffset"/>
<Property Name="Contents"/>
<Property Name="Value"/>
</Struct>
<Dictionary Name="AtSpiAttributeSet">
<Key Name="Key"/>
<Value Name="Value"/>
</Dictionary>
<Dictionary Name="AtSpiEventProperties">
<Key Name="Key"/>
<Value Name="Value"/>
</Dictionary>
<Struct Name="AtSpiDeviceEvent">
<Property Name="Type"/>
<Property Name="Id"/>
<Property Name="HardwareCode"/>
<Property Name="Modifiers">
<BitFlags Type="AtSpiModifierMask"/>
</Property>
<Property Name="Timestamp"/>
<Property Name="Text"/>
<Property Name="IsText"/>
</Struct>
<Struct Name="AtSpiMatchRule">
<Property Name="States">
<List/>
</Property>
<Property Name="StateMatchType"/>
<Property Name="Attributes" Type="AtSpiAttributeSet"/>
<Property Name="AttributeMatchType"/>
<Property Name="Roles">
<List/>
</Property>
<Property Name="RoleMatchType"/>
<Property Name="Interfaces">
<List/>
</Property>
<Property Name="InterfaceMatchType"/>
<Property Name="Invert"/>
</Struct>
<Struct Name="AtSpiKeyDefinition">
<Property Name="KeyCode"/>
<Property Name="KeySym"/>
<Property Name="KeyString"/>
<Property Name="Unused"/>
</Struct>
<Struct Name="AtSpiEventMode">
<Property Name="Synchronous"/>
<Property Name="Preemptive"/>
<Property Name="Global"/>
</Struct>
<BitFlags Name="AtSpiEventTypeMask">
<BitFlag Name="KeyPress" Value="1"/>
<BitFlag Name="KeyRelease" Value="2"/>
</BitFlags>
<BitFlags Name="AtSpiModifierMask">
<BitFlag Name="Shift" Value="1"/>
<BitFlag Name="Lock" Value="2"/>
<BitFlag Name="Control" Value="4"/>
<BitFlag Name="Mod1" Value="8"/>
<BitFlag Name="Mod2" Value="16"/>
<BitFlag Name="Mod3" Value="32"/>
<BitFlag Name="Mod4" Value="64"/>
<BitFlag Name="Mod5" Value="128"/>
</BitFlags>
<Struct Name="AtSpiKeystrokeListener">
<Property Name="BusName"/>
<Property Name="Path"/>
<Property Name="Type"/>
<Property Name="Types">
<BitFlags Type="AtSpiEventTypeMask"/>
</Property>
<Property Name="Keys">
<List>
<Struct Type="AtSpiKeyDefinition"/>
</List>
</Property>
<Property Name="Mask">
<BitFlags Type="AtSpiModifierMask"/>
</Property>
<Property Name="Mode" Type="AtSpiEventMode"/>
</Struct>
<Struct Name="AtSpiTextSelection">
<Property Name="StartObject" Type="AtSpiObjectReference"/>
<Property Name="StartOffset"/>
<Property Name="EndObject" Type="AtSpiObjectReference"/>
<Property Name="EndOffset"/>
<Property Name="IsStartActive"/>
</Struct>
<Struct Name="AtSpiRect">
<Property Name="X"/>
<Property Name="Y"/>
<Property Name="Width"/>
<Property Name="Height"/>
</Struct>
<Struct Name="AtSpiPoint">
<Property Name="X"/>
<Property Name="Y"/>
</Struct>
</Types>

47
src/Avalonia.FreeDesktop.AtSpi/DBusXml/Value.xml

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" xmlns:av="http://avaloniaui.net/dbus/1.0">
<av:ImportTypes>Types.xml</av:ImportTypes>
<!--
org.a11y.atspi.Value:
@short_description: An interface supporting a one-dimensional scalar
to be modified, or which reflects its value.
-->
<interface name="org.a11y.atspi.Value">
<!--
Version: The version of this interface.
This property is incremented by one every time a new method, signal, or property
is added to this interface.
-->
<property name="version" type="u" access="read"/>
<!--
MinimumValue: The minimum allowed value.
-->
<property name="MinimumValue" type="d" access="read"/>
<!--
MaximumValue: The maximum allowed value.
-->
<property name="MaximumValue" type="d" access="read"/>
<!--
MinimumIncrement: The minimum increment by which a value can be
adjusted.
-->
<property name="MinimumIncrement" type="d" access="read"/>
<!--
CurrentValue: The current value.
-->
<property name="CurrentValue" type="d" access="readwrite"/>
<!--
Text: a human readable text alternative associated with the value,
if available.
-->
<property name="Text" type="s" access="read"/>
</interface>
</node>

9
src/Avalonia.FreeDesktop.AtSpi/DBusXml/org.a11y.Bus.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:av="http://avaloniaui.net/dbus/1.0">
<av:ImportTypes>Types.xml</av:ImportTypes>
<interface name="org.a11y.Bus">
<method name="GetAddress">
<arg direction="out" type="s"/>
</method>
</interface>
</node>

134
src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiAccessibleHandler.cs

@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.FreeDesktop.AtSpi.DBusXml;
using static Avalonia.FreeDesktop.AtSpi.AtSpiConstants;
namespace Avalonia.FreeDesktop.AtSpi.Handlers
{
internal sealed class AtSpiAccessibleHandler : IOrgA11yAtspiAccessible
{
private readonly AtSpiServer _server;
private readonly AtSpiNode _node;
public AtSpiAccessibleHandler(AtSpiServer server, AtSpiNode node)
{
_server = server;
_node = node;
}
public uint Version => AccessibleVersion;
public string Name => _node.InvokeSync(() => _node.Peer.GetName());
public string Description => _node.InvokeSync(() => _node.Peer.GetHelpText());
public AtSpiObjectReference Parent
{
get
{
var parent = _node.InvokeSync(() => _node.Peer.GetParent());
var parentNode = AtSpiNode.TryGet(parent);
return _server.GetReference(parentNode);
}
}
public int ChildCount => _node.InvokeSync(() => _node.Peer.GetChildren().Count);
public string Locale => ResolveLocale();
public string AccessibleId => _node.InvokeSync(() => _node.Peer.GetAutomationId() ?? string.Empty);
public string HelpText => _node.InvokeSync(() => _node.Peer.GetHelpText());
public ValueTask<AtSpiObjectReference> GetChildAtIndexAsync(int index)
{
var children = _node.InvokeSync(() => _node.Peer.GetChildren());
if (index >= 0 && index < children.Count)
{
var childNode = AtSpiNode.GetOrCreate(children[index], _server);
return ValueTask.FromResult(_server.GetReference(childNode));
}
return ValueTask.FromResult(_server.GetNullReference());
}
public ValueTask<List<AtSpiObjectReference>> GetChildrenAsync()
{
var children = _node.InvokeSync(() => _node.Peer.GetChildren());
var refs = new List<AtSpiObjectReference>(children.Count);
foreach (var child in children)
{
var childNode = AtSpiNode.GetOrCreate(child, _server);
refs.Add(_server.GetReference(childNode));
}
return ValueTask.FromResult(refs);
}
public ValueTask<int> GetIndexInParentAsync()
{
var index = _node.InvokeSync(() =>
{
var parent = _node.Peer.GetParent();
if (parent is null)
return -1;
var siblings = parent.GetChildren();
for (var i = 0; i < siblings.Count; i++)
{
if (ReferenceEquals(siblings[i], _node.Peer))
return i;
}
return -1;
});
return ValueTask.FromResult(index);
}
public ValueTask<List<AtSpiRelationEntry>> GetRelationSetAsync()
{
return ValueTask.FromResult(new List<AtSpiRelationEntry>());
}
public ValueTask<uint> GetRoleAsync()
{
var role = _node.InvokeSync(() => AtSpiNode.ToAtSpiRole(_node.Peer.GetAutomationControlType()));
return ValueTask.FromResult((uint)role);
}
public ValueTask<string> GetRoleNameAsync()
{
var role = _node.InvokeSync(() => AtSpiNode.ToAtSpiRole(_node.Peer.GetAutomationControlType()));
return ValueTask.FromResult(AtSpiNode.ToAtSpiRoleName(role));
}
public ValueTask<string> GetLocalizedRoleNameAsync()
{
var role = _node.InvokeSync(() => AtSpiNode.ToAtSpiRole(_node.Peer.GetAutomationControlType()));
return ValueTask.FromResult(AtSpiNode.ToAtSpiRoleName(role));
}
public ValueTask<List<uint>> GetStateAsync()
{
return ValueTask.FromResult(_node.ComputeStates());
}
public ValueTask<AtSpiAttributeSet> GetAttributesAsync()
{
return ValueTask.FromResult(new AtSpiAttributeSet());
}
public ValueTask<AtSpiObjectReference> GetApplicationAsync()
{
return ValueTask.FromResult(_server.GetRootReference());
}
public ValueTask<List<string>> GetInterfacesAsync()
{
var interfaces = _node.InvokeSync(() => _node.GetSupportedInterfaces());
var sorted = interfaces.OrderBy(static i => i, StringComparer.Ordinal).ToList();
return ValueTask.FromResult(sorted);
}
}
}

112
src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiActionHandler.cs

@ -0,0 +1,112 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Automation;
using Avalonia.Automation.Provider;
using Avalonia.FreeDesktop.AtSpi.DBusXml;
using static Avalonia.FreeDesktop.AtSpi.AtSpiConstants;
namespace Avalonia.FreeDesktop.AtSpi.Handlers
{
internal sealed class AtSpiActionHandler : IOrgA11yAtspiAction
{
private readonly AtSpiNode _node;
private readonly List<ActionEntry> _actions;
public AtSpiActionHandler(AtSpiServer server, AtSpiNode node)
{
_ = server;
_node = node;
_actions = BuildActionList();
}
public uint Version => ActionVersion;
public int NActions => _actions.Count;
public ValueTask<string> GetDescriptionAsync(int index)
{
if (index >= 0 && index < _actions.Count)
return ValueTask.FromResult(_actions[index].Description);
return ValueTask.FromResult(string.Empty);
}
public ValueTask<string> GetNameAsync(int index)
{
if (index >= 0 && index < _actions.Count)
return ValueTask.FromResult(_actions[index].ActionName);
return ValueTask.FromResult(string.Empty);
}
public ValueTask<string> GetLocalizedNameAsync(int index)
{
if (index >= 0 && index < _actions.Count)
return ValueTask.FromResult(_actions[index].LocalizedName);
return ValueTask.FromResult(string.Empty);
}
public ValueTask<string> GetKeyBindingAsync(int index)
{
return ValueTask.FromResult(string.Empty);
}
public ValueTask<List<AtSpiAction>> GetActionsAsync()
{
var result = new List<AtSpiAction>(_actions.Count);
foreach (var entry in _actions)
result.Add(new AtSpiAction(entry.LocalizedName, entry.Description, string.Empty));
return ValueTask.FromResult(result);
}
public ValueTask<bool> DoActionAsync(int index)
{
if (index < 0 || index >= _actions.Count)
return ValueTask.FromResult(false);
var action = _actions[index];
_node.InvokeSync(() => ExecuteAction(action.ActionName));
return ValueTask.FromResult(true);
}
private void ExecuteAction(string actionName)
{
switch (actionName)
{
case "click":
_node.Peer.GetProvider<IInvokeProvider>()?.Invoke();
break;
case "toggle":
_node.Peer.GetProvider<IToggleProvider>()?.Toggle();
break;
case "expand":
_node.Peer.GetProvider<IExpandCollapseProvider>()?.Expand();
break;
case "collapse":
_node.Peer.GetProvider<IExpandCollapseProvider>()?.Collapse();
break;
}
}
private List<ActionEntry> BuildActionList()
{
var actions = new List<ActionEntry>();
if (_node.Peer.GetProvider<IInvokeProvider>() is not null)
actions.Add(new ActionEntry("click", "Click", "Performs the default action"));
if (_node.Peer.GetProvider<IToggleProvider>() is not null)
actions.Add(new ActionEntry("toggle", "Toggle", "Toggles the control state"));
if (_node.Peer.GetProvider<IExpandCollapseProvider>() is { } expandCollapse)
{
if (expandCollapse.ExpandCollapseState == ExpandCollapseState.Collapsed)
actions.Add(new ActionEntry("expand", "Expand", "Expands the control"));
else
actions.Add(new ActionEntry("collapse", "Collapse", "Collapses the control"));
}
return actions;
}
private readonly record struct ActionEntry(string ActionName, string LocalizedName, string Description);
}
}

39
src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiApplicationHandler.cs

@ -0,0 +1,39 @@
using System.Threading.Tasks;
using Avalonia.FreeDesktop.AtSpi.DBusXml;
using static Avalonia.FreeDesktop.AtSpi.AtSpiConstants;
namespace Avalonia.FreeDesktop.AtSpi.Handlers
{
internal sealed class AtSpiApplicationHandler : IOrgA11yAtspiApplication
{
public AtSpiApplicationHandler(AtSpiServer server, AtSpiNode node)
{
_ = server;
_ = node;
var version = ResolveToolkitVersion();
ToolkitName = "Avalonia";
Version = version;
ToolkitVersion = version;
AtspiVersion = "2.1";
InterfaceVersion = ApplicationVersion;
}
public string ToolkitName { get; }
public string Version { get; }
public string ToolkitVersion { get; }
public string AtspiVersion { get; }
public uint InterfaceVersion { get; }
public int Id { get; set; }
public ValueTask<string> GetLocaleAsync(uint lctype)
{
return ValueTask.FromResult(ResolveLocale());
}
public ValueTask<string> GetApplicationBusAddressAsync()
{
return ValueTask.FromResult(string.Empty);
}
}
}

219
src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiComponentHandler.cs

@ -0,0 +1,219 @@
using System.Threading.Tasks;
using Avalonia.Automation.Provider;
using Avalonia.FreeDesktop.AtSpi.DBusXml;
using Avalonia.Platform;
using static Avalonia.FreeDesktop.AtSpi.AtSpiConstants;
namespace Avalonia.FreeDesktop.AtSpi.Handlers
{
internal sealed class AtSpiComponentHandler : IOrgA11yAtspiComponent
{
private readonly AtSpiServer _server;
private readonly AtSpiNode _node;
public AtSpiComponentHandler(AtSpiServer server, AtSpiNode node)
{
_server = server;
_node = node;
}
public uint Version => ComponentVersion;
public ValueTask<bool> ContainsAsync(int x, int y, uint coordType)
{
var rect = GetScreenExtents();
var point = TranslateToScreen(x, y, coordType);
var contains = point.x >= rect.X && point.y >= rect.Y &&
point.x < rect.X + rect.Width && point.y < rect.Y + rect.Height;
return ValueTask.FromResult(contains);
}
public ValueTask<AtSpiObjectReference> GetAccessibleAtPointAsync(int x, int y, uint coordType)
{
// Basic hit testing - return self for now
var rect = GetScreenExtents();
var point = TranslateToScreen(x, y, coordType);
if (point.x >= rect.X && point.y >= rect.Y &&
point.x < rect.X + rect.Width && point.y < rect.Y + rect.Height)
{
return ValueTask.FromResult(_server.GetReference(_node));
}
return ValueTask.FromResult(_server.GetNullReference());
}
public ValueTask<AtSpiRect> GetExtentsAsync(uint coordType)
{
var rect = GetScreenExtents();
var translated = TranslateRect(rect, coordType);
return ValueTask.FromResult(new AtSpiRect(
(int)translated.X, (int)translated.Y,
(int)translated.Width, (int)translated.Height));
}
public ValueTask<(int X, int Y)> GetPositionAsync(uint coordType)
{
var rect = GetScreenExtents();
var translated = TranslateRect(rect, coordType);
return ValueTask.FromResult(((int)translated.X, (int)translated.Y));
}
public ValueTask<(int Width, int Height)> GetSizeAsync()
{
var rect = GetScreenExtents();
return ValueTask.FromResult(((int)rect.Width, (int)rect.Height));
}
public ValueTask<uint> GetLayerAsync()
{
var controlType = _node.InvokeSync(() => _node.Peer.GetAutomationControlType());
// Window layer = 7, Widget layer = 3
var layer = controlType == Automation.Peers.AutomationControlType.Window ? 7u : 3u;
return ValueTask.FromResult(layer);
}
public ValueTask<short> GetMDIZOrderAsync() => ValueTask.FromResult((short)-1);
public ValueTask<bool> GrabFocusAsync()
{
_node.InvokeSync(() => _node.Peer.SetFocus());
return ValueTask.FromResult(true);
}
public ValueTask<double> GetAlphaAsync() => ValueTask.FromResult(1.0);
public ValueTask<bool> SetExtentsAsync(int x, int y, int width, int height, uint coordType)
{
return ValueTask.FromResult(false);
}
public ValueTask<bool> SetPositionAsync(int x, int y, uint coordType)
{
return ValueTask.FromResult(false);
}
public ValueTask<bool> SetSizeAsync(int width, int height)
{
return ValueTask.FromResult(false);
}
public ValueTask<bool> ScrollToAsync(uint type)
{
return ValueTask.FromResult(false);
}
public ValueTask<bool> ScrollToPointAsync(uint coordType, int x, int y)
{
return ValueTask.FromResult(false);
}
private Rect GetScreenExtents()
{
return _node.InvokeSync(() =>
{
var bounds = _node.Peer.GetBoundingRectangle();
if (_node is RootAtSpiNode rootNode)
return rootNode.ToScreen(bounds);
// Find root and translate to screen
var root = _node.Peer.GetVisualRoot();
if (root is not null)
{
var rootNode2 = AtSpiNode.TryGet(root) as RootAtSpiNode;
if (rootNode2 is not null)
return rootNode2.ToScreen(bounds);
}
return bounds;
});
}
private Rect TranslateRect(Rect screenRect, uint coordType)
{
// coordType: 0 = screen, 1 = window, 2 = parent
if (coordType == 0)
return screenRect;
if (coordType == 1)
{
var windowRect = GetWindowRect();
return new Rect(
screenRect.X - windowRect.X,
screenRect.Y - windowRect.Y,
screenRect.Width,
screenRect.Height);
}
if (coordType == 2)
{
var parentRect = GetParentScreenRect();
return new Rect(
screenRect.X - parentRect.X,
screenRect.Y - parentRect.Y,
screenRect.Width,
screenRect.Height);
}
return screenRect;
}
private (int x, int y) TranslateToScreen(int x, int y, uint coordType)
{
if (coordType == 0)
return (x, y);
if (coordType == 1)
{
var windowRect = GetWindowRect();
return (x + (int)windowRect.X, y + (int)windowRect.Y);
}
if (coordType == 2)
{
var parentRect = GetParentScreenRect();
return (x + (int)parentRect.X, y + (int)parentRect.Y);
}
return (x, y);
}
private Rect GetWindowRect()
{
return _node.InvokeSync(() =>
{
var root = _node.Peer.GetVisualRoot();
if (root is not null)
{
var rootNode = AtSpiNode.TryGet(root) as RootAtSpiNode;
if (rootNode is not null)
return rootNode.ToScreen(root.GetBoundingRectangle());
}
return default;
});
}
private Rect GetParentScreenRect()
{
return _node.InvokeSync(() =>
{
var parent = _node.Peer.GetParent();
if (parent is not null)
{
var bounds = parent.GetBoundingRectangle();
var root = parent.GetVisualRoot();
if (root is not null)
{
var rootNode = AtSpiNode.TryGet(root) as RootAtSpiNode;
if (rootNode is not null)
return rootNode.ToScreen(bounds);
}
return bounds;
}
return default;
});
}
}
}

57
src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiEventObjectHandler.cs

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using Avalonia.DBus;
using Avalonia.FreeDesktop.AtSpi.DBusXml;
using static Avalonia.FreeDesktop.AtSpi.AtSpiConstants;
namespace Avalonia.FreeDesktop.AtSpi.Handlers
{
internal sealed class AtSpiEventObjectHandler : IOrgA11yAtspiEventObject
{
private readonly AtSpiServer _server;
private readonly string _path;
public AtSpiEventObjectHandler(AtSpiServer server, string path)
{
_server = server;
_path = path;
}
public uint Version => EventObjectVersion;
public void EmitChildrenChangedSignal(string operation, int indexInParent, DBusVariant child)
{
EmitSignal("ChildrenChanged", operation, indexInParent, 0, child, EmptyProperties());
}
public void EmitPropertyChangeSignal(string propertyName, DBusVariant value)
{
EmitSignal("PropertyChange", propertyName, 0, 0, value, EmptyProperties());
}
public void EmitStateChangedSignal(string stateName, int detail1, DBusVariant value)
{
EmitSignal("StateChanged", stateName, detail1, 0, value, EmptyProperties());
}
private void EmitSignal(string member, params object[] body)
{
var connection = _server.A11yConnection;
if (connection is null)
return;
var message = DBusMessage.CreateSignal(
(DBusObjectPath)_path,
IfaceEventObject,
member,
body);
_ = connection.SendMessageAsync(message);
}
private static Dictionary<string, DBusVariant> EmptyProperties()
{
return new Dictionary<string, DBusVariant>(StringComparer.Ordinal);
}
}
}

41
src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiValueHandler.cs

@ -0,0 +1,41 @@
using System;
using Avalonia.Automation.Provider;
using Avalonia.FreeDesktop.AtSpi.DBusXml;
using static Avalonia.FreeDesktop.AtSpi.AtSpiConstants;
namespace Avalonia.FreeDesktop.AtSpi.Handlers
{
internal sealed class AtSpiValueHandler : IOrgA11yAtspiValue
{
private readonly AtSpiNode _node;
public AtSpiValueHandler(AtSpiServer server, AtSpiNode node)
{
_ = server;
_node = node;
}
public uint Version => ValueVersion;
public double MinimumValue => _node.InvokeSync<IRangeValueProvider, double>(p => p.Minimum);
public double MaximumValue => _node.InvokeSync<IRangeValueProvider, double>(p => p.Maximum);
public double MinimumIncrement => _node.InvokeSync<IRangeValueProvider, double>(p => p.SmallChange);
public string Text => string.Empty;
public double CurrentValue
{
get => _node.InvokeSync<IRangeValueProvider, double>(p => p.Value);
set
{
_node.InvokeSync<IRangeValueProvider>(p =>
{
var clamped = Math.Max(p.Minimum, Math.Min(p.Maximum, value));
p.SetValue(clamped);
});
}
}
}
}

46
src/Avalonia.FreeDesktop.AtSpi/RootAtSpiNode.cs

@ -0,0 +1,46 @@
using System;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Provider;
using Avalonia.Platform;
using static Avalonia.FreeDesktop.AtSpi.AtSpiConstants;
namespace Avalonia.FreeDesktop.AtSpi
{
internal sealed class RootAtSpiNode : AtSpiNode
{
public RootAtSpiNode(AutomationPeer peer, AtSpiServer server)
: base(peer, server, RootPath)
{
RootProvider = peer.GetProvider<IRootProvider>() ?? throw new InvalidOperationException(
"Attempt to create RootAtSpiNode from peer which does not implement IRootProvider.");
RootProvider.FocusChanged += OnRootFocusChanged;
}
public IRootProvider RootProvider { get; }
public IWindowBaseImpl? WindowImpl => RootProvider.PlatformImpl as IWindowBaseImpl;
public Rect ToScreen(Rect rect)
{
if (WindowImpl is null)
return default;
return new PixelRect(
WindowImpl.PointToScreen(rect.TopLeft),
WindowImpl.PointToScreen(rect.BottomRight))
.ToRect(1);
}
public Point PointToClient(PixelPoint point)
{
if (WindowImpl is null)
return default;
return WindowImpl.PointToClient(point);
}
private void OnRootFocusChanged(object? sender, EventArgs e)
{
var focused = InvokeSync(() => RootProvider.GetFocus());
var focusedNode = focused is not null ? GetOrCreate(focused, Server) : null;
Server.EmitFocusChange(focusedNode);
}
}
}
Loading…
Cancel
Save