From d957cb391e4dfb827e636bd691b6318cfaf5ebd5 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sun, 1 Mar 2026 02:56:07 +0800 Subject: [PATCH] Fix missing root automation peer in x11/atspi (#20775) * Fix missing root automation peer in x11/atspi * Root nodes are special case; fix GetIndexInParentAsync to handle root nodes since their parents == null. --- .../Handlers/AtSpiAccessibleHandler.cs | 16 ++++++++++++++++ src/Avalonia.X11/X11AtSpiAccessibility.cs | 7 +++++-- src/Avalonia.X11/X11Window.cs | 12 +++++++----- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiAccessibleHandler.cs b/src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiAccessibleHandler.cs index ee79116f4b..4e674a9ed5 100644 --- a/src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiAccessibleHandler.cs +++ b/src/Avalonia.FreeDesktop.AtSpi/Handlers/AtSpiAccessibleHandler.cs @@ -63,6 +63,22 @@ namespace Avalonia.FreeDesktop.AtSpi.Handlers public ValueTask GetIndexInParentAsync() { + // Window nodes are children of the ApplicationAtSpiNode, but their + // internal Parent field is null (they are attached with parent: null). + // Mirror the Parent property's special case so that backward path + // walks (e.g. accerciser's get_index_in_parent) work correctly. + if (node is RootAtSpiNode { AppRoot: { } appRoot }) + { + var windows = appRoot.WindowChildren; + for (var i = 0; i < windows.Count; i++) + { + if (ReferenceEquals(windows[i], node)) + return ValueTask.FromResult(i); + } + + return ValueTask.FromResult(-1); + } + var parent = node.Parent; if (parent is null) return ValueTask.FromResult(-1); diff --git a/src/Avalonia.X11/X11AtSpiAccessibility.cs b/src/Avalonia.X11/X11AtSpiAccessibility.cs index 18278da34b..cc51dfe9e7 100644 --- a/src/Avalonia.X11/X11AtSpiAccessibility.cs +++ b/src/Avalonia.X11/X11AtSpiAccessibility.cs @@ -142,8 +142,11 @@ namespace Avalonia.X11 { try { - if (window.InputRoot.RootElement is Control control) - return ControlAutomationPeer.CreatePeerForElement(control); + if (window.InputRoot.FocusRoot is Control control) + { + var peer = ControlAutomationPeer.CreatePeerForElement(control); + return peer?.GetAutomationRoot() ?? peer; + } } catch (Exception e) { diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 1e032f446f..11dff2f2a5 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1029,9 +1029,10 @@ namespace Avalonia.X11 // Remove from AT-SPI tree before closing _platform.UntrackWindow(this); if (_platform.AtSpiServer is { } atSpiServer - && _inputRoot?.RootElement is Control atSpiControl) + && _inputRoot?.FocusRoot is Control atSpiControl) { - var atSpiPeer = atSpiControl.GetAutomationPeer(); + var atSpiPeer = atSpiControl.GetAutomationPeer()?.GetAutomationRoot() + ?? atSpiControl.GetAutomationPeer(); if (atSpiPeer is not null) atSpiServer.RemoveWindow(atSpiPeer); } @@ -1125,11 +1126,12 @@ namespace Avalonia.X11 _platform.TrackWindow(this); if (_platform.AtSpiServer is { } server - && _inputRoot?.RootElement is Control c) + && _inputRoot?.FocusRoot is Control c) { var peer = Avalonia.Automation.Peers.ControlAutomationPeer.CreatePeerForElement(c); - if (peer is not null) - server.AddWindow(peer); + var rootPeer = peer?.GetAutomationRoot() ?? peer; + if (rootPeer is not null) + server.AddWindow(rootPeer); } }