use std::future::{Future, poll_fn};
use std::pin::pin;
use std::sync::{Mutex, PoisonError};
use std::task::Poll;
use std::time::{Duration, Instant};
use zbus::zvariant::{OwnedValue, Value};
const PORTAL: &str = "org.freedesktop.portal.Desktop";
const PORTAL_PATH: &str = "/org/freedesktop/portal/desktop";
const SETTINGS_INTERFACE: &str = "org.freedesktop.portal.Settings";
const PROPERTIES_INTERFACE: &str = "org.freedesktop.DBus.Properties";
const UNKNOWN_METHOD: &str = "org.freedesktop.DBus.Error.UnknownMethod";
pub(crate) const CALL_TIMEOUT: Duration = Duration::from_millis(500);
const QUIET_PERIOD: Duration = Duration::from_secs(5);
static SESSION: Bus = Bus::session();
pub(crate) fn read_portal_u32(namespace: &str, key: &str) -> Option<u32> {
SESSION.read_portal_u32(namespace, key)
}
pub(crate) fn read_portal_rgb(namespace: &str, key: &str) -> Option<(f64, f64, f64)> {
SESSION.read_portal_rgb(namespace, key)
}
pub(crate) fn read_bool_property(
service: &str,
path: &str,
interface: &str,
property: &str,
) -> Option<bool> {
SESSION.read_bool_property(service, path, interface, property)
}
pub(crate) fn decode_u32(value: &Value<'_>) -> Option<u32> {
match value {
Value::U32(v) => Some(*v),
_ => None,
}
}
pub(crate) fn decode_rgb(value: &Value<'_>) -> Option<(f64, f64, f64)> {
let Value::Structure(structure) = value else {
return None;
};
let [Value::F64(r), Value::F64(g), Value::F64(b)] = structure.fields() else {
return None;
};
let in_range = |c: f64| (0.0..=1.0).contains(&c);
(in_range(*r) && in_range(*g) && in_range(*b)).then_some((*r, *g, *b))
}
pub(crate) fn decode_bool(value: &Value<'_>) -> Option<bool> {
match value {
Value::Bool(v) => Some(*v),
_ => None,
}
}
pub(crate) fn read_reply_value<'v>(value: &'v Value<'v>) -> Option<&'v Value<'v>> {
match value {
Value::Value(inner) => Some(inner),
_ => None,
}
}
#[derive(Debug, Default)]
struct QuietList {
entries: Vec<(String, Instant)>,
}
impl QuietList {
const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
fn is_quiet(&self, name: &str, now: Instant) -> bool {
self.entries
.iter()
.any(|(entry, until)| entry == name && now < *until)
}
fn silence(&mut self, name: &str, until: Instant) {
match self.entries.iter_mut().find(|(entry, _)| entry == name) {
Some((_, entry_until)) => *entry_until = until,
None => self.entries.push((name.to_owned(), until)),
}
}
}
const BUS_ITSELF: &str = "";
pub(crate) struct Bus {
address: Option<String>,
state: Mutex<BusState>,
}
struct BusState {
connection: Option<zbus::Connection>,
quiet: QuietList,
}
#[derive(Debug, PartialEq, Eq)]
enum CallError {
UnknownMethod,
Failed,
}
impl Bus {
const fn session() -> Self {
Self::with_address(None)
}
#[cfg(test)]
pub(crate) fn at(address: String) -> Self {
Self::with_address(Some(address))
}
const fn with_address(address: Option<String>) -> Self {
Self {
address,
state: Mutex::new(BusState {
connection: None,
quiet: QuietList::new(),
}),
}
}
pub(crate) fn read_portal_u32(&self, namespace: &str, key: &str) -> Option<u32> {
self.read_setting(namespace, key, decode_u32)
}
pub(crate) fn read_portal_rgb(&self, namespace: &str, key: &str) -> Option<(f64, f64, f64)> {
self.read_setting(namespace, key, decode_rgb)
}
pub(crate) fn read_bool_property(
&self,
service: &str,
path: &str,
interface: &str,
property: &str,
) -> Option<bool> {
let value = self
.call(
service,
path,
PROPERTIES_INTERFACE,
"Get",
&(interface, property),
)
.ok()?;
decode_bool(&value)
}
fn read_setting<T>(
&self,
namespace: &str,
key: &str,
decode: impl Fn(&Value<'_>) -> Option<T>,
) -> Option<T> {
let body = (namespace, key);
match self.call(PORTAL, PORTAL_PATH, SETTINGS_INTERFACE, "ReadOne", &body) {
Ok(value) => decode(&value),
Err(CallError::UnknownMethod) => {
let value = self
.call(PORTAL, PORTAL_PATH, SETTINGS_INTERFACE, "Read", &body)
.ok()?;
decode(read_reply_value(&value)?)
}
Err(CallError::Failed) => None,
}
}
fn lock(&self) -> std::sync::MutexGuard<'_, BusState> {
self.state.lock().unwrap_or_else(PoisonError::into_inner)
}
fn connection(&self) -> Option<zbus::Connection> {
let mut state = self.lock();
if let Some(connection) = &state.connection {
return Some(connection.clone());
}
let now = Instant::now();
if state.quiet.is_quiet(BUS_ITSELF, now) {
return None;
}
let address = self.address.as_deref();
let connected = async_io::block_on(within(CALL_TIMEOUT, async move {
let builder = match address {
Some(address) => zbus::connection::Builder::address(address),
None => zbus::connection::Builder::session(),
};
builder
.ok()?
.method_timeout(CALL_TIMEOUT)
.build()
.await
.ok()
}))
.flatten();
match connected {
Some(connection) => {
state.connection = Some(connection.clone());
Some(connection)
}
None => {
state
.quiet
.silence(BUS_ITSELF, Instant::now() + QUIET_PERIOD);
None
}
}
}
fn call<B>(
&self,
destination: &str,
path: &str,
interface: &str,
method: &str,
body: &B,
) -> Result<OwnedValue, CallError>
where
B: zbus::export::serde::Serialize + zbus::zvariant::DynamicType,
{
if self.lock().quiet.is_quiet(destination, Instant::now()) {
return Err(CallError::Failed);
}
let connection = self.connection().ok_or(CallError::Failed)?;
let reply = async_io::block_on(connection.call_method(
Some(destination),
path,
Some(interface),
method,
body,
));
match reply {
Ok(message) => message
.body()
.deserialize::<OwnedValue>()
.map_err(|_| CallError::Failed),
Err(zbus::Error::MethodError(name, _, _)) if name.as_str() == UNKNOWN_METHOD => {
Err(CallError::UnknownMethod)
}
Err(zbus::Error::MethodError(..) | zbus::Error::FDO(_)) => Err(CallError::Failed),
Err(zbus::Error::InputOutput(error))
if error.kind() == std::io::ErrorKind::TimedOut =>
{
self.lock()
.quiet
.silence(destination, Instant::now() + QUIET_PERIOD);
Err(CallError::Failed)
}
Err(zbus::Error::InputOutput(_)) => {
self.lock().connection = None;
Err(CallError::Failed)
}
Err(_) => Err(CallError::Failed),
}
}
}
pub(crate) async fn within<T>(limit: Duration, future: impl Future<Output = T>) -> Option<T> {
let mut future = pin!(future);
let mut timer = pin!(async_io::Timer::after(limit));
poll_fn(|cx| {
if let Poll::Ready(output) = future.as_mut().poll(cx) {
return Poll::Ready(Some(output));
}
if timer.as_mut().poll(cx).is_ready() {
return Poll::Ready(None);
}
Poll::Pending
})
.await
}
#[cfg(test)]
pub(crate) mod tests;