Skip to content

Commit

Permalink
Merge pull request #202 from odilia-app/object-proxies-guardeed-lazy-…
Browse files Browse the repository at this point in the history
…conversions

Add lazy, cheaply guarded proxy-conversions.
  • Loading branch information
TTWNO authored Jan 2, 2025
2 parents c91e749 + 4c59c24 commit 0a4a49d
Show file tree
Hide file tree
Showing 4 changed files with 328 additions and 2 deletions.
8 changes: 7 additions & 1 deletion atspi-common/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub enum AtspiError {
/// On specific types, if the kind (string variant) does not match the Event's kind.
KindMatch(String),

/// When an interface is not available.
InterfaceNotAvailable(&'static str),

/// To indicate a match or equality test on a signal body signature failed.
SignatureMatch(String),

Expand Down Expand Up @@ -90,6 +93,9 @@ impl std::fmt::Display for AtspiError {
Self::SignatureMatch(e) => {
f.write_str(format!("atspi: body signature mismatch in conversion: {e:?}").as_str())
}
Self::InterfaceNotAvailable(e) => {
f.write_str(format!("atspi: interface not available: {e}").as_str())
}
Self::UnknownInterface => f.write_str("Unknown interface."),
Self::MissingInterface => f.write_str("Missing interface."),
Self::MissingMember => f.write_str("Missing member."),
Expand Down Expand Up @@ -197,7 +203,7 @@ impl std::fmt::Display for ObjectPathConversionError {
match self {
Self::NoIdAvailable => f.write_str("No ID available in the path."),
Self::ParseError(e) => {
f.write_str("Failure to parse: {e}")?;
f.write_str("Failure to parse: ")?;
e.fmt(f)
}
}
Expand Down
4 changes: 3 additions & 1 deletion atspi-common/src/role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,9 @@ pub mod tests {
.unwrap_or_else(|_| panic!("Unable to encode {from_role}"));
println!("ENCODED: {encoded:?}");

let (zbus_role, _) = encoded.deserialize().expect("Unable to decode {encoded:?}");
let (zbus_role, _) = encoded
.deserialize()
.unwrap_or_else(|_| panic!("Unable to decode {encoded:?}"));

assert_eq!(from_role, zbus_role, "The serde `Data::deserialize` and `From<u32>` impls produced different results. The number used was {role_num}, it produced a Role of {from_role}, but the from_slice(...) implementation produced {zbus_role}");
assert_eq!(
Expand Down
1 change: 1 addition & 0 deletions atspi-proxies/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub mod device_event_controller;
pub mod device_event_listener;
pub mod document;
pub mod editable_text;
pub mod proxy_ext;
pub use common::{events, AtspiError, CoordType, Interface, InterfaceSet};

pub mod hyperlink;
Expand Down
317 changes: 317 additions & 0 deletions atspi-proxies/src/proxy_ext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
use crate::{
accessible::AccessibleProxy, action::ActionProxy, application::ApplicationProxy,
cache::CacheProxy, collection::CollectionProxy, component::ComponentProxy,
document::DocumentProxy, editable_text::EditableTextProxy, hyperlink::HyperlinkProxy,
hypertext::HypertextProxy, image::ImageProxy, selection::SelectionProxy, table::TableProxy,
table_cell::TableCellProxy, text::TextProxy, value::ValueProxy, AtspiError,
};
use atspi_common::{Interface, InterfaceSet, Result};

/// Easily acquire the other interface proxies an object may have.
///
/// Equip objects with conversions to proxies of the objects' further implemented interfaces
/// by extending `AccessibleProxy`.
///
/// The `proxies` method returns a `Proxies` struct, which contains lazily loaded proxy accessors.
///
/// # Lazy initialization and cheap checks
///
/// Proxies are lazily initialized, so they are only created when requested.
/// Interface availability is checked before creating the proxy and is a cheap bitop.
pub trait ProxyExt<'a> {
/// Get `Proxies` for the current object.
fn proxies(&self) -> impl std::future::Future<Output = Result<Proxies<'a>>>;
}

/// An object for safe conversion to the related interface proxies.
#[derive(Clone, Debug)]
pub struct Proxies<'a> {
interfaces: InterfaceSet,
proxy: zbus::Proxy<'a>,
inner: InnerProxies<'a>,
}

#[derive(Clone, Debug, Default)]
struct InnerProxies<'a> {
action: Option<ActionProxy<'a>>,
application: Option<ApplicationProxy<'a>>,
cache: Option<CacheProxy<'a>>,
collection: Option<CollectionProxy<'a>>,
component: Option<ComponentProxy<'a>>,
document: Option<DocumentProxy<'a>>,
editable_text: Option<EditableTextProxy<'a>>,
hyperlink: Option<HyperlinkProxy<'a>>,
hypertext: Option<HypertextProxy<'a>>,
image: Option<ImageProxy<'a>>,
selection: Option<SelectionProxy<'a>>,
table: Option<TableProxy<'a>>,
table_cell: Option<TableCellProxy<'a>>,
text: Option<TextProxy<'a>>,
value: Option<ValueProxy<'a>>,
}

impl<'a> ProxyExt<'a> for AccessibleProxy<'a> {
async fn proxies(&self) -> Result<Proxies<'a>> {
let iface_set: InterfaceSet = self.get_interfaces().await?;
let proxy = self.inner().clone();

Ok(Proxies { interfaces: iface_set, proxy, inner: InnerProxies::default() })
}
}

impl<'a> Proxies<'a> {
/// Get the `Action` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn action(&mut self) -> Result<&mut ActionProxy<'a>> {
if self.interfaces.contains(Interface::Action) {
let proxy_ref = self
.inner
.action
.get_or_insert_with(|| ActionProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Action"))
}
}

/// Get the `Application` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn application(&mut self) -> Result<&mut ApplicationProxy<'a>> {
if self.interfaces.contains(Interface::Application) {
let proxy_ref = self
.inner
.application
.get_or_insert_with(|| ApplicationProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Application"))
}
}

/// Get the `Cache` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn cache(&mut self) -> Result<&mut CacheProxy<'a>> {
if self.interfaces.contains(Interface::Cache) {
let proxy_ref = self
.inner
.cache
.get_or_insert_with(|| CacheProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Cache"))
}
}

/// Get the `Collection` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn collection(&mut self) -> Result<&mut CollectionProxy<'a>> {
if self.interfaces.contains(Interface::Collection) {
let proxy_ref = self
.inner
.collection
.get_or_insert_with(|| CollectionProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Collection"))
}
}

/// Get the `Component` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn component(&mut self) -> Result<&mut ComponentProxy<'a>> {
if self.interfaces.contains(Interface::Component) {
let proxy_ref = self
.inner
.component
.get_or_insert_with(|| ComponentProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Component"))
}
}

/// Get the `Document` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn document(&mut self) -> Result<&mut DocumentProxy<'a>> {
if self.interfaces.contains(Interface::Document) {
let proxy_ref = self
.inner
.document
.get_or_insert_with(|| DocumentProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Document"))
}
}

/// Get the `EditableText` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn editable_text(&mut self) -> Result<&mut EditableTextProxy<'a>> {
if self.interfaces.contains(Interface::EditableText) {
let proxy_ref = self
.inner
.editable_text
.get_or_insert_with(|| EditableTextProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("EditableText"))
}
}

/// Get the `Hyperlink` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn hyperlink(&mut self) -> Result<&mut HyperlinkProxy<'a>> {
if self.interfaces.contains(Interface::Hyperlink) {
let proxy_ref = self
.inner
.hyperlink
.get_or_insert_with(|| HyperlinkProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Hyperlink"))
}
}

/// Get the `Hypertext` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn hypertext(&mut self) -> Result<&mut HypertextProxy<'a>> {
if self.interfaces.contains(Interface::Hypertext) {
let proxy_ref = self
.inner
.hypertext
.get_or_insert_with(|| HypertextProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Hypertext"))
}
}

/// Get the `Image` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn image(&mut self) -> Result<&mut ImageProxy<'a>> {
if self.interfaces.contains(Interface::Image) {
let proxy_ref = self
.inner
.image
.get_or_insert_with(|| ImageProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Image"))
}
}

/// Get the `Registry` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn selection(&mut self) -> Result<&mut SelectionProxy<'a>> {
if self.interfaces.contains(Interface::Selection) {
let proxy_ref = self
.inner
.selection
.get_or_insert_with(|| SelectionProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Selection"))
}
}

/// Get the `Table` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn table(&mut self) -> Result<&mut TableProxy<'a>> {
if self.interfaces.contains(Interface::Table) {
let proxy_ref = self
.inner
.table
.get_or_insert_with(|| TableProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Table"))
}
}

/// Get the `TableCell` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn table_cell(&mut self) -> Result<&mut TableCellProxy<'a>> {
if self.interfaces.contains(Interface::TableCell) {
let proxy_ref = self
.inner
.table_cell
.get_or_insert_with(|| TableCellProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("TableCell"))
}
}

/// Get the `Text` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn text(&mut self) -> Result<&mut TextProxy<'a>> {
if self.interfaces.contains(Interface::Text) {
let proxy_ref = self
.inner
.text
.get_or_insert_with(|| TextProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Text"))
}
}

/// Get the `Value` interface proxy.
///
/// # Errors
///
/// Returns an error if the interface is not available.
pub fn value(&mut self) -> Result<&mut ValueProxy<'a>> {
if self.interfaces.contains(Interface::Value) {
let proxy_ref = self
.inner
.value
.get_or_insert_with(|| ValueProxy::from(self.proxy.clone()));
Ok(proxy_ref)
} else {
Err(AtspiError::InterfaceNotAvailable("Value"))
}
}
}

0 comments on commit 0a4a49d

Please sign in to comment.