#![deny(clippy::all, clippy::pedantic, clippy::cargo, unsafe_code, rustdoc::all, missing_docs)]
#![allow(clippy::multiple_crate_versions)]
pub use atspi_common as common;
use common::{
error::AtspiError,
events::{DBusInterface, DBusMatchRule, DBusMember, MessageConversion, RegistryEventString},
EventProperties, Result as AtspiResult,
};
#[cfg(feature = "wrappers")]
use atspi_common::events::Event;
#[cfg(feature = "p2p")]
mod p2p;
#[cfg(feature = "p2p")]
pub use p2p::{Peer, P2P};
use atspi_proxies::{
accessible::AccessibleProxy,
bus::{BusProxy, StatusProxy},
registry::RegistryProxy,
};
#[cfg(feature = "wrappers")]
use futures_lite::stream::{Stream, StreamExt};
use std::ops::Deref;
use zbus::{
fdo::DBusProxy,
proxy::{CacheProperties, Defaults},
Address, MatchRule,
};
#[cfg(feature = "wrappers")]
use zbus::{message::Type as MessageType, MessageStream};
#[cfg(feature = "p2p")]
use crate::p2p::Peers;
#[derive(Clone, Debug)]
pub struct AccessibilityConnection {
registry: RegistryProxy<'static>,
dbus_proxy: DBusProxy<'static>,
#[cfg(feature = "p2p")]
peers: Peers,
}
impl AccessibilityConnection {
#[cfg_attr(feature = "tracing", tracing::instrument)]
pub async fn new() -> AtspiResult<Self> {
let a11y_bus_addr = {
#[cfg(feature = "tracing")]
tracing::debug!("Connecting to session bus");
let session_bus = Box::pin(zbus::Connection::session()).await?;
#[cfg(feature = "tracing")]
tracing::debug!(
name = session_bus.unique_name().map(|n| n.as_str()),
"Connected to session bus"
);
let proxy = BusProxy::new(&session_bus).await?;
#[cfg(feature = "tracing")]
tracing::debug!("Getting a11y bus address from session bus");
proxy.get_address().await?
};
#[cfg(feature = "tracing")]
tracing::debug!(address = %a11y_bus_addr, "Got a11y bus address");
let addr: Address = a11y_bus_addr.parse()?;
let accessibility_conn = Self::from_address(addr).await?;
#[cfg(feature = "p2p")]
accessibility_conn
.peers
.spawn_peer_listener_task(accessibility_conn.connection());
Ok(accessibility_conn)
}
pub async fn from_address(bus_addr: Address) -> AtspiResult<Self> {
#[cfg(feature = "tracing")]
tracing::info!("Connecting to a11y bus");
let bus = Box::pin(zbus::connection::Builder::address(bus_addr)?.build()).await?;
#[cfg(feature = "tracing")]
tracing::info!(name = bus.unique_name().map(|n| n.as_str()), "Connected to a11y bus");
let registry = RegistryProxy::new(&bus).await?;
let dbus_proxy = DBusProxy::new(&bus).await?;
#[cfg(not(feature = "p2p"))]
return Ok(Self { registry, dbus_proxy });
#[cfg(feature = "p2p")]
let peers = Peers::initialize_peers(&bus).await?;
#[cfg(feature = "p2p")]
return Ok(Self { registry, dbus_proxy, peers });
}
#[cfg(feature = "wrappers")]
pub fn event_stream(&self) -> impl Stream<Item = Result<Event, AtspiError>> {
MessageStream::from(self.registry.inner().connection()).filter_map(|res| {
let msg = match res {
Ok(m) => m,
Err(e) => return Some(Err(e.into())),
};
let msg_header = msg.header();
let Some(msg_interface) = msg_header.interface() else {
return Some(Err(AtspiError::MissingInterface));
};
if msg_interface.starts_with("org.a11y.atspi")
&& msg.message_type() == MessageType::Signal
{
Some(Event::try_from(&msg))
} else {
None
}
})
}
pub async fn add_match_rule<T: DBusMatchRule>(&self) -> Result<(), AtspiError> {
let match_rule = MatchRule::try_from(<T as DBusMatchRule>::MATCH_RULE_STRING)?;
self.dbus_proxy.add_match_rule(match_rule).await?;
Ok(())
}
pub async fn remove_match_rule<T: DBusMatchRule>(&self) -> Result<(), AtspiError> {
let match_rule = MatchRule::try_from(<T as DBusMatchRule>::MATCH_RULE_STRING)?;
self.dbus_proxy.add_match_rule(match_rule).await?;
Ok(())
}
pub async fn add_registry_event<T: RegistryEventString>(&self) -> Result<(), AtspiError> {
self.registry
.register_event(<T as RegistryEventString>::REGISTRY_EVENT_STRING)
.await?;
Ok(())
}
pub async fn remove_registry_event<T: RegistryEventString>(&self) -> Result<(), AtspiError> {
self.registry
.deregister_event(<T as RegistryEventString>::REGISTRY_EVENT_STRING)
.await?;
Ok(())
}
pub async fn register_event<T: RegistryEventString + DBusMatchRule>(
&self,
) -> Result<(), AtspiError> {
self.add_registry_event::<T>().await?;
self.add_match_rule::<T>().await?;
Ok(())
}
pub async fn deregister_event<T: RegistryEventString + DBusMatchRule>(
&self,
) -> Result<(), AtspiError> {
self.remove_registry_event::<T>().await?;
self.remove_match_rule::<T>().await?;
Ok(())
}
#[must_use = "The reference to the underlying zbus::Connection must be used"]
pub fn connection(&self) -> &zbus::Connection {
self.registry.inner().connection()
}
pub async fn send_event<'a, T>(&self, event: T) -> Result<(), AtspiError>
where
T: DBusMember + DBusInterface + EventProperties + MessageConversion<'a>,
{
let conn = self.connection();
let new_message = zbus::Message::signal(
event.path(),
<T as DBusInterface>::DBUS_INTERFACE,
<T as DBusMember>::DBUS_MEMBER,
)?
.sender(conn.unique_name().ok_or(AtspiError::MissingName)?)?
.build(&event.body())?;
Ok(conn.send(&new_message).await?)
}
pub async fn root_accessible_on_registry(&self) -> Result<AccessibleProxy<'_>, AtspiError> {
let registry_well_known_name = RegistryProxy::DESTINATION
.as_ref()
.expect("Registry default_service is set");
let registry = AccessibleProxy::builder(self.connection())
.destination(registry_well_known_name)?
.cache_properties(CacheProperties::No)
.build()
.await?;
Ok(registry)
}
}
impl Deref for AccessibilityConnection {
type Target = RegistryProxy<'static>;
fn deref(&self) -> &Self::Target {
&self.registry
}
}
pub async fn set_session_accessibility(status: bool) -> std::result::Result<(), AtspiError> {
let session = Box::pin(zbus::Connection::session()).await?;
let status_proxy = StatusProxy::new(&session).await?;
if status_proxy.is_enabled().await? != status {
status_proxy.set_is_enabled(status).await?;
}
Ok(())
}
pub async fn read_session_accessibility() -> AtspiResult<bool> {
let session = Box::pin(zbus::Connection::session()).await?;
let status_proxy = StatusProxy::new(&session).await?;
status_proxy.is_enabled().await.map_err(Into::into)
}