diff --git a/Cargo.lock b/Cargo.lock index aa781d8d..79044f9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -351,20 +351,34 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atspi" -version = "0.21.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0e6aa3f7b617b3fd296defcbd186159e8182f968805ce4b509f8aa2bddaa30" +checksum = "c9212da2086f77502f3f384c2c802705f0681cc4937be2417978f8e6243099b3" dependencies = [ + "atspi-client", "atspi-common", "atspi-connection", "atspi-proxies", ] +[[package]] +name = "atspi-client" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464e7a0e29d619091668329fe3b0de0351227f9a7bf36ce3c79e2c417f744c5" +dependencies = [ + "async-trait", + "atspi-common", + "atspi-proxies", + "static_assertions", + "zbus", +] + [[package]] name = "atspi-common" -version = "0.5.0" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67f7c733947d5eb53e7b6c740b8822428813c9f6fd8745c02aa890fde87b5dd6" +checksum = "d03467fb79afd9e86e63aa908d542df63171abb02e66cb4e28978ec1e1da7870" dependencies = [ "enumflags2", "serde", @@ -376,23 +390,42 @@ dependencies = [ [[package]] name = "atspi-connection" -version = "0.5.0" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2980b9b690d2a76554cc3e3a984ff69c79ba7cc100f919c39f443780437b81bc" +checksum = "8531aef8ca768b5ecc601b2838dba66b7142f24c5dde587be0900aef96eaf43f" dependencies = [ "atspi-common", "atspi-proxies", - "futures-lite 2.2.0", + "futures-lite 1.13.0", "zbus", ] +[[package]] +name = "atspi-macros" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a1ad0c51b69e83d6f551707598e0b53d14336b16867d0372df31210556c0b0" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "serde", + "syn 1.0.109", + "zbus", + "zvariant", +] + [[package]] name = "atspi-proxies" -version = "0.5.0" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0619da7b6b282cea5a94b8536526659fccab4a0677cee1d05c6783b37ae5a2e8" +checksum = "c539f2c42b6f9c513902c55cf1c48c78140a186b63feb77df1845ef8464275a0" dependencies = [ + "async-trait", "atspi-common", + "atspi-macros", + "enumflags2", "serde", "zbus", ] @@ -1473,6 +1506,7 @@ name = "odilia" version = "0.1.4" dependencies = [ "atspi", + "atspi-client", "atspi-common", "atspi-connection", "atspi-proxies", @@ -1507,6 +1541,7 @@ version = "0.3.0" dependencies = [ "async-trait", "atspi", + "atspi-client", "atspi-common", "atspi-connection", "atspi-proxies", @@ -1957,6 +1992,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-xml-rs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0bf1ba0696ccf0872866277143ff1fd14d22eec235d2b23702f95e6660f7dfa" +dependencies = [ + "log", + "serde", + "thiserror", + "xml-rs", +] + [[package]] name = "serde_derive" version = "1.0.197" @@ -2781,6 +2828,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "xml-rs" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" + [[package]] name = "zbus" version = "3.15.2" @@ -2810,6 +2863,7 @@ dependencies = [ "ordered-stream", "rand", "serde", + "serde-xml-rs", "serde_repr", "sha1", "static_assertions", diff --git a/Cargo.toml b/Cargo.toml index 7fb6fcc3..5c657972 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,10 +31,11 @@ pre-release-hook = ["cargo", "fmt"] dependent-version = "upgrade" [workspace.dependencies] -atspi = { version = "0.21.0", default-features = false, features = ["tokio"] } -atspi-proxies = { version = "0.5.0", default-features = false, features = ["tokio"] } -atspi-common = { version = "0.5.0", default-features = false, features = ["tokio"] } -atspi-connection = { version = "0.5.0", default-features = false, features = ["tokio"] } +atspi = { version = "0.16.0", default-features = false, features = ["tokio", "unstable-proxy-traits"] } +atspi-client = { version = "0.1.0", default-features = false } +atspi-proxies = { version = "0.1.0", default-features = false } +atspi-common = { version = "0.1.0", default-features = false } +atspi-connection = { version = "0.1.0", default-features = false } odilia-common = { version = "0.3.0", path = "./common" } odilia-cache = { version = "0.3.0", path = "./cache" } eyre = "0.6.8" diff --git a/cache/Cargo.toml b/cache/Cargo.toml index 24324c4b..dceedd42 100644 --- a/cache/Cargo.toml +++ b/cache/Cargo.toml @@ -15,6 +15,7 @@ categories = ["accessibility"] atspi.workspace = true atspi-proxies.workspace = true atspi-common.workspace = true +atspi-client.workspace = true odilia-common.workspace = true dashmap = "5.4.0" serde = "1.0.147" diff --git a/cache/src/convertable.rs b/cache/src/convertable.rs deleted file mode 100644 index eb73c8a4..00000000 --- a/cache/src/convertable.rs +++ /dev/null @@ -1,162 +0,0 @@ -use async_trait::async_trait; -use atspi::Interface; -use atspi_proxies::{ - accessible::AccessibleProxy, - action::ActionProxy, - application::ApplicationProxy, - 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, - AtspiProxy, -}; -use std::ops::Deref; -use zbus::{ - blocking::Proxy as ProxyBlocking, blocking::ProxyBuilder as ProxyBuilderBlocking, - CacheProperties, Error, Proxy, ProxyBuilder, ProxyDefault, -}; - -#[allow(clippy::module_name_repetitions)] -#[async_trait] -pub trait Convertable { - type Error: std::error::Error; - - /// Creates an [`Self::Accessible`] from the existing accessible item. - /// # Errors - /// - /// This may fail based on the implementation of. - /// Generally, it fails if the accessible item does not implement to accessible interface. - /// This shouldn't be possible, but this function may fail for other reasons. - /// For example, to convert a [`zbus::Proxy`] into a [`Self::Accessible`], it may fail to create the new [`atspi_proxies::accessible::AccessibleProxy`]. - async fn to_accessible(&self) -> Result; - /// Creates an [`Self::Action`] from the existing accessible item. - /// # Errors - /// - /// This may fail based on the implementation. - /// Generally, it fails if the accessible item does not implement to action interface. - async fn to_action(&self) -> Result; - /// Creates an [`Self::Application`] from the existing accessible item. - /// # Errors - /// - /// This may fail based on the implementation. - /// Generally, it fails if the accessible item does not implement to application interface. - async fn to_application(&self) -> Result; - /// Creates an [`Collection`] from the existing accessible item. - /// # Errors - /// - /// This may fail based on the implementation. - /// GenerallyProxy, it fails if the accessible item does not implement to collection interface. - async fn to_collection(&self) -> Result; - /// Creates an [`Component`] from the existing accessible item. - /// # Errors - /// - /// This may fail based on the implementation. - /// GenerallyProxy, it fails if the accessible item does not implement to component interface. - async fn to_component(&self) -> Result; - async fn to_document(&self) -> Result; - async fn to_hypertext(&self) -> Result; - async fn to_hyperlink(&self) -> Result; - async fn to_image(&self) -> Result; - async fn to_selection(&self) -> Result; - async fn to_table(&self) -> Result; - async fn to_table_cell(&self) -> Result; - async fn to_text(&self) -> Result; - async fn to_editable_text(&self) -> Result; - async fn to_value(&self) -> Result; -} - -#[inline] -async fn convert_to_new_type< - 'a, - 'b, - T: From> + ProxyDefault, - U: Deref> + ProxyDefault, ->( - from: &U, -) -> zbus::Result { - // first thing is first, we need to creat an accessible to query the interfaces. - let accessible = AccessibleProxy::builder(from.connection()) - .destination(from.destination())? - .cache_properties(CacheProperties::No) - .path(from.path())? - .build() - .await?; - // if the interface we're trying to convert to is not available as an interface; this can be problematic because the interface we're passing in could potentially be different from what we're converting to. - let new_interface_name = Interface::try_from(::INTERFACE).map_err(|_| Error::InterfaceNotFound)?; - if !accessible - .get_interfaces() - .await? - .contains(new_interface_name) - { - return Err(Error::InterfaceNotFound); - } - // otherwise, make a new Proxy with the related type. - let path = from.path().to_owned(); - let dest = from.destination().to_owned(); - ProxyBuilder::<'b, T>::new_bare(from.connection()) - .interface(::INTERFACE)? - .destination(dest)? - .cache_properties(CacheProperties::No) - .path(path)? - .build() - .await -} - -#[async_trait] -impl<'a, T: Deref> + ProxyDefault + Sync> Convertable for T { - type Error = zbus::Error; - /* no guard due to assumption it is always possible */ - async fn to_accessible(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_action(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_application(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_collection(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_component(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_document(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_hypertext(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_hyperlink(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_image(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_selection(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_table(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_table_cell(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_text(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_editable_text(&self) -> zbus::Result { - convert_to_new_type(self).await - } - async fn to_value(&self) -> zbus::Result { - convert_to_new_type(self).await - } -} diff --git a/cache/src/lib.rs b/cache/src/lib.rs index 4415e2aa..06a23f2b 100644 --- a/cache/src/lib.rs +++ b/cache/src/lib.rs @@ -8,21 +8,19 @@ )] #![allow(clippy::multiple_crate_versions)] -mod convertable; -use convertable::Convertable; - use std::{ collections::HashMap, sync::{Arc, RwLock, Weak}, }; use async_trait::async_trait; +use atspi_client::{convertable::Convertable, text_ext::TextExt}; use atspi_common::{ - ClipType, CoordType, GenericEvent, Granularity, InterfaceSet, RelationType, Role, StateSet, object_ref::ObjectRef, + ClipType, CoordType, GenericEvent, Granularity, InterfaceSet, RelationType, Role, StateSet, }; use atspi_proxies::{ - accessible::AccessibleProxy, - text::TextProxy, + accessible::{Accessible, AccessibleProxy}, + text::{Text, TextProxy}, }; use dashmap::DashMap; use fxhash::FxBuildHasher; @@ -37,16 +35,6 @@ use zbus::{ CacheProperties, ProxyBuilder, }; -trait AllText { - async fn get_all_text(&self) -> Result; -} -impl AllText for TextProxy<'_> { - async fn get_all_text(&self) -> Result { - let length_of_string = self.character_count().await?; - Ok(self.get_text(0, length_of_string).await?) - } -} - type CacheKey = AccessiblePrimitive; type InnerCache = DashMap>, FxBuildHasher>; type ThreadSafeCache = Arc; @@ -117,23 +105,29 @@ impl AccessiblePrimitive { Ok(Self { id, sender: sender.as_str().into() }) } } -impl From for AccessiblePrimitive { - fn from( - atspi_accessible: ObjectRef, - ) -> AccessiblePrimitive { +impl TryFrom for AccessiblePrimitive { + type Error = AccessiblePrimitiveConversionError; + + #[tracing::instrument(level = "trace", ret, err)] + fn try_from( + atspi_accessible: atspi::events::Accessible, + ) -> Result { let tuple_converter = (atspi_accessible.name, atspi_accessible.path); - tuple_converter.into() + tuple_converter.try_into() } } -impl From<(OwnedUniqueName, OwnedObjectPath)> for AccessiblePrimitive { - fn from( +impl TryFrom<(OwnedUniqueName, OwnedObjectPath)> for AccessiblePrimitive { + type Error = AccessiblePrimitiveConversionError; + + #[tracing::instrument(level = "trace", ret, err)] + fn try_from( so: (OwnedUniqueName, OwnedObjectPath), - ) -> AccessiblePrimitive { + ) -> Result { let accessible_id = so.1; - AccessiblePrimitive { + Ok(AccessiblePrimitive { id: accessible_id.to_string(), sender: so.0.as_str().into(), - } + }) } } impl From<(String, OwnedObjectPath)> for AccessiblePrimitive { @@ -344,31 +338,48 @@ fn strong_cache(weak_cache: &Weak) -> OdiliaResult> { Weak::upgrade(weak_cache).ok_or(OdiliaError::Cache(CacheError::NotAvailable)) } -impl CacheItem { - async fn get_application(&self) -> Result { +#[async_trait] +impl Accessible for CacheItem { + type Error = OdiliaError; + async fn get_application(&self) -> Result { let derefed_cache: Arc = strong_cache(&self.cache)?; derefed_cache.get(&self.app).ok_or(CacheError::NoItem.into()) } - async fn parent(&self) -> Result { + async fn parent(&self) -> Result { let parent_item = self .parent .clone_inner() .or_else(|| self.cache.upgrade()?.get(&self.parent.key)); parent_item.ok_or(CacheError::NoItem.into()) } - async fn get_attributes(&self) -> Result, OdiliaError> { + async fn get_children(&self) -> Result, Self::Error> { + self.get_children() + } + async fn child_count(&self) -> Result { + Ok(self.children_num) + } + async fn get_index_in_parent(&self) -> Result { + Ok(self.index) + } + async fn get_role(&self) -> Result { + Ok(self.role) + } + async fn get_interfaces(&self) -> Result { + Ok(self.interfaces) + } + async fn get_attributes(&self) -> Result, Self::Error> { Ok(as_accessible(self).await?.get_attributes().await?) } - async fn name(&self) -> Result { + async fn name(&self) -> Result { Ok(as_accessible(self).await?.name().await?) } - async fn locale(&self) -> Result { + async fn locale(&self) -> Result { Ok(as_accessible(self).await?.locale().await?) } - async fn description(&self) -> Result { + async fn description(&self) -> Result { Ok(as_accessible(self).await?.description().await?) } - async fn get_relation_set(&self) -> Result)>, OdiliaError> { + async fn get_relation_set(&self) -> Result)>, Self::Error> { let cache = strong_cache(&self.cache)?; as_accessible(self) .await? @@ -393,39 +404,42 @@ impl CacheItem { .map(|(relation, result_selfs)| Ok((relation, result_selfs?))) .collect::)>, OdiliaError>>() } - async fn get_role_name(&self) -> Result { + async fn get_role_name(&self) -> Result { Ok(as_accessible(self).await?.get_role_name().await?) } - async fn get_state(&self) -> Result { + async fn get_state(&self) -> Result { Ok(self.states) } - async fn get_child_at_index(&self, idx: i32) -> Result { - self.get_children()? + async fn get_child_at_index(&self, idx: i32) -> Result { + ::get_children(self) + .await? .get(usize::try_from(idx)?) .ok_or(CacheError::NoItem.into()) .cloned() } - async fn get_localized_role_name(&self) -> Result { + async fn get_localized_role_name(&self) -> Result { Ok(as_accessible(self).await?.get_localized_role_name().await?) } - async fn accessible_id(&self) -> Result { + async fn accessible_id(&self) -> Result { Ok(self.object.id.to_string()) } } +#[async_trait] +impl Text for CacheItem { + type Error = OdiliaError; -impl CacheItem { async fn add_selection( &self, start_offset: i32, end_offset: i32, - ) -> Result { + ) -> Result { Ok(as_text(self).await?.add_selection(start_offset, end_offset).await?) } async fn get_attribute_run( &self, offset: i32, include_defaults: bool, - ) -> Result<(std::collections::HashMap, i32, i32), OdiliaError> { + ) -> Result<(std::collections::HashMap, i32, i32), Self::Error> { Ok(as_text(self) .await? .get_attribute_run(offset, include_defaults) @@ -435,16 +449,16 @@ impl CacheItem { &self, offset: i32, attribute_name: &str, - ) -> Result { + ) -> Result { Ok(as_text(self) .await? .get_attribute_value(offset, attribute_name) .await?) } - async fn get_text_attributes( + async fn get_attributes( &self, offset: i32, - ) -> Result<(std::collections::HashMap, i32, i32), OdiliaError> { + ) -> Result<(std::collections::HashMap, i32, i32), Self::Error> { Ok(as_text(self).await?.get_attributes(offset).await?) } async fn get_bounded_ranges( @@ -456,7 +470,7 @@ impl CacheItem { coord_type: CoordType, x_clip_type: ClipType, y_clip_type: ClipType, - ) -> Result, OdiliaError> { + ) -> Result, Self::Error> { Ok(as_text(self) .await? .get_bounded_ranges( @@ -470,27 +484,27 @@ impl CacheItem { ) .await?) } - async fn get_character_at_offset(&self, offset: i32) -> Result { + async fn get_character_at_offset(&self, offset: i32) -> Result { Ok(as_text(self).await?.get_character_at_offset(offset).await?) } async fn get_character_extents( &self, offset: i32, coord_type: CoordType, - ) -> Result<(i32, i32, i32, i32), OdiliaError> { + ) -> Result<(i32, i32, i32, i32), Self::Error> { Ok(as_text(self).await?.get_character_extents(offset, coord_type).await?) } async fn get_default_attribute_set( &self, - ) -> Result, OdiliaError> { + ) -> Result, Self::Error> { Ok(as_text(self).await?.get_default_attribute_set().await?) } async fn get_default_attributes( &self, - ) -> Result, OdiliaError> { + ) -> Result, Self::Error> { Ok(as_text(self).await?.get_default_attributes().await?) } - async fn get_nselections(&self) -> Result { + async fn get_nselections(&self) -> Result { Ok(as_text(self).await?.get_nselections().await?) } async fn get_offset_at_point( @@ -498,7 +512,7 @@ impl CacheItem { x: i32, y: i32, coord_type: CoordType, - ) -> Result { + ) -> Result { Ok(as_text(self).await?.get_offset_at_point(x, y, coord_type).await?) } async fn get_range_extents( @@ -506,20 +520,20 @@ impl CacheItem { start_offset: i32, end_offset: i32, coord_type: CoordType, - ) -> Result<(i32, i32, i32, i32), OdiliaError> { + ) -> Result<(i32, i32, i32, i32), Self::Error> { Ok(as_text(self) .await? .get_range_extents(start_offset, end_offset, coord_type) .await?) } - async fn get_selection(&self, selection_num: i32) -> Result<(i32, i32), OdiliaError> { + async fn get_selection(&self, selection_num: i32) -> Result<(i32, i32), Self::Error> { Ok(as_text(self).await?.get_selection(selection_num).await?) } async fn get_string_at_offset( &self, offset: i32, granularity: Granularity, - ) -> Result<(String, i32, i32), OdiliaError> { + ) -> Result<(String, i32, i32), Self::Error> { let uoffset = usize::try_from(offset)?; // optimisations that don't call out to DBus. if granularity == Granularity::Paragraph { @@ -581,40 +595,34 @@ impl CacheItem { &self, start_offset: i32, end_offset: i32, - ) -> Result { + ) -> Result { self.text .get(usize::try_from(start_offset)?..usize::try_from(end_offset)?) .map(std::borrow::ToOwned::to_owned) .ok_or(OdiliaError::Generic("Type is None, not Some".to_string())) } - async fn get_all_text( - &self - ) -> Result { - let length_of_string = self.character_count().await?; - Ok(self.get_text(0, length_of_string).await?) - } async fn get_text_after_offset( &self, offset: i32, type_: u32, - ) -> Result<(String, i32, i32), OdiliaError> { + ) -> Result<(String, i32, i32), Self::Error> { Ok(as_text(self).await?.get_text_after_offset(offset, type_).await?) } async fn get_text_at_offset( &self, offset: i32, type_: u32, - ) -> Result<(String, i32, i32), OdiliaError> { + ) -> Result<(String, i32, i32), Self::Error> { Ok(as_text(self).await?.get_text_at_offset(offset, type_).await?) } async fn get_text_before_offset( &self, offset: i32, type_: u32, - ) -> Result<(String, i32, i32), OdiliaError> { + ) -> Result<(String, i32, i32), Self::Error> { Ok(as_text(self).await?.get_text_before_offset(offset, type_).await?) } - async fn remove_selection(&self, selection_num: i32) -> Result { + async fn remove_selection(&self, selection_num: i32) -> Result { Ok(as_text(self).await?.remove_selection(selection_num).await?) } async fn scroll_substring_to( @@ -622,7 +630,7 @@ impl CacheItem { start_offset: i32, end_offset: i32, type_: u32, - ) -> Result { + ) -> Result { Ok(as_text(self) .await? .scroll_substring_to(start_offset, end_offset, type_) @@ -635,13 +643,13 @@ impl CacheItem { type_: u32, x: i32, y: i32, - ) -> Result { + ) -> Result { Ok(as_text(self) .await? .scroll_substring_to_point(start_offset, end_offset, type_, x, y) .await?) } - async fn set_caret_offset(&self, offset: i32) -> Result { + async fn set_caret_offset(&self, offset: i32) -> Result { Ok(as_text(self).await?.set_caret_offset(offset).await?) } async fn set_selection( @@ -649,16 +657,16 @@ impl CacheItem { selection_num: i32, start_offset: i32, end_offset: i32, - ) -> Result { + ) -> Result { Ok(as_text(self) .await? .set_selection(selection_num, start_offset, end_offset) .await?) } - async fn caret_offset(&self) -> Result { + async fn caret_offset(&self) -> Result { Ok(as_text(self).await?.caret_offset().await?) } - async fn character_count(&self) -> Result { + async fn character_count(&self) -> Result { Ok(i32::try_from(self.text.len())?) } } diff --git a/odilia/Cargo.toml b/odilia/Cargo.toml index 25a27c97..ef339b56 100644 --- a/odilia/Cargo.toml +++ b/odilia/Cargo.toml @@ -34,6 +34,7 @@ atspi.workspace = true atspi-proxies.workspace = true atspi-common.workspace = true atspi-connection.workspace = true +atspi-client.workspace = true circular-queue = "^0.2.6" eyre.workspace = true futures = { version = "^0.3.25", default-features = false }