From 0d72f153b0475c29b3e8ed35bbcd3feb2cd251af Mon Sep 17 00:00:00 2001 From: Arnold Loubriat Date: Wed, 26 Aug 2026 00:59:21 +0200 Subject: [PATCH] feat: Support the EditableText interface on Unix --- adapters/atspi-common/src/node.rs | 28 ++++++++++ adapters/atspi-common/src/simplified.rs | 14 +++++ adapters/unix/src/atspi/bus.rs | 8 +++ .../src/atspi/interfaces/editable_text.rs | 52 +++++++++++++++++++ adapters/unix/src/atspi/interfaces/mod.rs | 2 + 5 files changed, 104 insertions(+) create mode 100644 adapters/unix/src/atspi/interfaces/editable_text.rs diff --git a/adapters/atspi-common/src/node.rs b/adapters/atspi-common/src/node.rs index c84677c68..97c403768 100644 --- a/adapters/atspi-common/src/node.rs +++ b/adapters/atspi-common/src/node.rs @@ -445,6 +445,10 @@ impl NodeWrapper<'_> { self.0.raw_bounds().is_some() || self.is_root() } + fn supports_editable_text(&self) -> bool { + self.0.is_text_input() && self.0.supports_text_ranges() + } + fn supports_hyperlink(&self) -> bool { self.0.supports_url() } @@ -469,6 +473,9 @@ impl NodeWrapper<'_> { if self.supports_component() { interfaces.insert(Interface::Component); } + if self.supports_editable_text() { + interfaces.insert(Interface::EditableText); + } if self.supports_hyperlink() { interfaces.insert(Interface::Hyperlink); } @@ -973,6 +980,10 @@ impl PlatformNode { }) } + pub fn supports_editable_text(&self) -> Result { + self.resolve(|node| Ok(NodeWrapper(&node).supports_editable_text())) + } + pub fn supports_hyperlink(&self) -> Result { self.resolve(|node| { let wrapper = NodeWrapper(&node); @@ -1143,6 +1154,23 @@ impl PlatformNode { Ok(true) } + pub fn set_text_contents(&self, value: &str) -> Result { + self.resolve_with_context(|node, tree, context| { + if node.is_read_only() { + return Ok(false); + } + let (target_node, target_tree) = + tree.state().locate_node(self.id).ok_or(Error::Defunct)?; + context.do_action(ActionRequest { + action: Action::SetValue, + target_tree, + target_node, + data: Some(ActionData::Value(value.into())), + }); + Ok(true) + }) + } + pub fn n_anchors(&self) -> Result { self.resolve(|node| if node.url().is_some() { Ok(1) } else { Ok(0) }) } diff --git a/adapters/atspi-common/src/simplified.rs b/adapters/atspi-common/src/simplified.rs index a369bb85b..f8241be10 100644 --- a/adapters/atspi-common/src/simplified.rs +++ b/adapters/atspi-common/src/simplified.rs @@ -238,6 +238,20 @@ impl Accessible { } } + pub fn supports_editable_text(&self) -> Result { + match self { + Self::Node(node) => node.supports_editable_text(), + Self::Root(_) => Ok(false), + } + } + + pub fn set_text_contents(&self, value: &str) -> Result { + match self { + Self::Node(node) => node.set_text_contents(value), + Self::Root(_) => Err(Error::UnsupportedInterface), + } + } + pub fn supports_hyperlink(&self) -> Result { match self { Self::Node(node) => node.supports_hyperlink(), diff --git a/adapters/unix/src/atspi/bus.rs b/adapters/unix/src/atspi/bus.rs index a4955b726..87567756a 100644 --- a/adapters/unix/src/atspi/bus.rs +++ b/adapters/unix/src/atspi/bus.rs @@ -141,6 +141,10 @@ impl Bus { ) .await?; } + if new_interfaces.contains(Interface::EditableText) { + self.register_interface(&path, EditableTextInterface::new(node.clone())) + .await?; + } if new_interfaces.contains(Interface::Hyperlink) { self.register_interface( &path, @@ -200,6 +204,10 @@ impl Bus { self.unregister_interface::(&path) .await?; } + if old_interfaces.contains(Interface::EditableText) { + self.unregister_interface::(&path) + .await?; + } if old_interfaces.contains(Interface::Hyperlink) { self.unregister_interface::(&path) .await?; diff --git a/adapters/unix/src/atspi/interfaces/editable_text.rs b/adapters/unix/src/atspi/interfaces/editable_text.rs new file mode 100644 index 000000000..098533a7f --- /dev/null +++ b/adapters/unix/src/atspi/interfaces/editable_text.rs @@ -0,0 +1,52 @@ +// Copyright 2026 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use accesskit_atspi_common::PlatformNode; +use zbus::{fdo, interface}; + +fn unsupported() -> fdo::Error { + fdo::Error::NotSupported("editing operation is not supported".into()) +} + +pub(crate) struct EditableTextInterface(PlatformNode); + +impl EditableTextInterface { + pub fn new(node: PlatformNode) -> Self { + Self(node) + } + + fn map_error(&self) -> impl '_ + FnOnce(accesskit_atspi_common::Error) -> fdo::Error { + |error| crate::util::map_error_from_node(&self.0, error) + } +} + +#[interface(name = "org.a11y.atspi.EditableText")] +impl EditableTextInterface { + fn set_text_contents(&self, new_contents: &str) -> fdo::Result { + self.0 + .set_text_contents(new_contents) + .map_err(self.map_error()) + } + + fn insert_text(&self, _position: i32, _text: &str, _length: i32) -> fdo::Result { + Err(unsupported()) + } + + fn copy_text(&self, _start_pos: i32, _end_pos: i32) -> fdo::Result<()> { + Err(unsupported()) + } + + fn cut_text(&self, _start_pos: i32, _end_pos: i32) -> fdo::Result { + Err(unsupported()) + } + + fn delete_text(&self, _start_pos: i32, _end_pos: i32) -> fdo::Result { + Err(unsupported()) + } + + fn paste_text(&self, _position: i32) -> fdo::Result { + Err(unsupported()) + } +} diff --git a/adapters/unix/src/atspi/interfaces/mod.rs b/adapters/unix/src/atspi/interfaces/mod.rs index 7e33c1e4d..b1d96a382 100644 --- a/adapters/unix/src/atspi/interfaces/mod.rs +++ b/adapters/unix/src/atspi/interfaces/mod.rs @@ -8,6 +8,7 @@ mod action; mod application; mod cache; mod component; +mod editable_text; mod hyperlink; mod selection; mod text; @@ -35,6 +36,7 @@ pub(crate) use action::*; pub(crate) use application::*; pub(crate) use cache::*; pub(crate) use component::*; +pub(crate) use editable_text::*; pub(crate) use hyperlink::*; pub(crate) use selection::*; pub(crate) use text::*;