#![cfg_attr(docsrs, feature(doc_cfg))]
use std::sync::{Arc, Weak};
#[cfg(feature = "blocking")]
#[cfg_attr(docsrs, doc(cfg(feature = "blocking")))]
pub mod blocking;
mod compat;
mod dbus_interface;
pub mod menu;
mod service;
mod tray;
#[doc(inline)]
pub use menu::{MenuItem, TextDirection};
pub use tray::{Category, Icon, Orientation, Status, ToolTip};
use crate::compat::{mpsc, oneshot, Mutex};
pub trait Tray: Sized + Send + 'static {
const MENU_ON_ACTIVATE: bool = false;
fn id(&self) -> String;
fn activate(&mut self, _x: i32, _y: i32) {}
fn secondary_activate(&mut self, _x: i32, _y: i32) {}
fn scroll(&mut self, _delta: i32, _orientation: Orientation) {}
fn category(&self) -> Category {
Category::ApplicationStatus
}
fn title(&self) -> String {
Default::default()
}
fn status(&self) -> Status {
Status::Active
}
fn window_id(&self) -> i32 {
0
}
fn icon_theme_path(&self) -> String {
Default::default()
}
fn icon_name(&self) -> String {
Default::default()
}
fn icon_pixmap(&self) -> Vec<Icon> {
Default::default()
}
fn overlay_icon_name(&self) -> String {
Default::default()
}
fn overlay_icon_pixmap(&self) -> Vec<Icon> {
Default::default()
}
fn attention_icon_name(&self) -> String {
Default::default()
}
fn attention_icon_pixmap(&self) -> Vec<Icon> {
Default::default()
}
fn attention_movie_name(&self) -> String {
Default::default()
}
fn tool_tip(&self) -> ToolTip {
Default::default()
}
fn text_direction(&self) -> TextDirection {
TextDirection::LeftToRight
}
fn menu(&self) -> Vec<MenuItem<Self>> {
Default::default()
}
fn watcher_online(&self) {}
#[allow(unused_variables)]
fn watcher_offline(&self, reason: OfflineReason) -> bool {
true
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum OfflineReason {
No,
Error(Error),
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Dbus(zbus::Error),
Watcher(zbus::fdo::Error),
WontShow,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Error::*;
match self {
Dbus(e) => write!(f, "D-Bus connection error: {e}"),
Watcher(e) => write!(f, "failed to register to the StatusNotifierWatcher: {e}"),
WontShow => write!(f, "no StatusNotifierHost exists"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use Error::*;
match self {
Dbus(e) => e.source(),
Watcher(e) => e.source(),
WontShow => None,
}
}
}
#[allow(async_fn_in_trait)]
pub trait TrayMethods: Tray + private::Sealed {
async fn spawn(self) -> Result<Handle<Self>, Error> {
TrayServiceBuilder::new(self).spawn().await
}
#[doc(hidden)]
#[deprecated(
note = "use `disable_dbus_name(true).spawn()` instead",
since = "0.3.4"
)]
async fn spawn_without_dbus_name(self) -> Result<Handle<Self>, Error> {
self.disable_dbus_name(true).spawn().await
}
fn disable_dbus_name(self, disable: bool) -> TrayServiceBuilder<Self> {
TrayServiceBuilder::new(self).disable_dbus_name(disable)
}
fn assume_sni_available(self, assume_available: bool) -> TrayServiceBuilder<Self> {
TrayServiceBuilder::new(self).assume_sni_available(assume_available)
}
}
impl<T: Tray> TrayMethods for T {}
fn _assert_tray_methods_returned_future_is_send<T: Tray + Clone>(x: T) {
fn assert_send<T: Send>(_: T) {}
assert_send(x.clone().spawn());
#[allow(deprecated)]
assert_send(x.clone().spawn_without_dbus_name());
}
mod private {
pub trait Sealed {}
impl<T: crate::Tray> Sealed for T {}
}
pub struct TrayServiceBuilder<T: Tray> {
tray: T,
own_name: bool,
assume_sni_available: bool,
}
impl<T: Tray> TrayServiceBuilder<T> {
fn new(tray: T) -> Self {
Self {
tray,
own_name: true,
assume_sni_available: false,
}
}
pub async fn spawn(self) -> Result<Handle<T>, Error> {
spawn_with_options(self.tray, self.own_name, self.assume_sni_available).await
}
pub fn disable_dbus_name(self, disable: bool) -> Self {
Self {
own_name: !disable,
..self
}
}
pub fn assume_sni_available(self, assume_available: bool) -> Self {
Self {
assume_sni_available: assume_available,
..self
}
}
}
async fn spawn_with_options<T: Tray>(
tray: T,
own_name: bool,
assume_sni_available: bool,
) -> Result<Handle<T>, Error> {
let (handle_tx, handle_rx) = mpsc::unbounded_channel();
let service = service::Service::new(tray);
let service_loop =
service::run(service.clone(), handle_rx, own_name, assume_sni_available).await?;
compat::spawn(service_loop);
Ok(Handle {
service: Arc::downgrade(&service),
sender: handle_tx,
})
}
pub(crate) enum HandleReuest {
Update(oneshot::Sender<()>),
Shutdown(oneshot::Sender<()>),
}
pub struct Handle<T> {
service: Weak<Mutex<service::Service<T>>>,
sender: mpsc::UnboundedSender<HandleReuest>,
}
impl<T> Handle<T> {
pub async fn update<R, F: FnOnce(&mut T) -> R>(&self, f: F) -> Option<R> {
if let Some(service) = self.service.upgrade() {
let r = f(&mut service.lock().await.tray);
let (tx, rx) = oneshot::channel();
if self.sender.send(HandleReuest::Update(tx)).is_ok() {
let _ = rx.await;
return Some(r);
}
}
None
}
pub fn shutdown(&self) -> ShutdownAwaiter {
let (tx, rx) = oneshot::channel();
if self.sender.send(HandleReuest::Shutdown(tx)).is_ok() {
ShutdownAwaiter::new(rx)
} else {
ShutdownAwaiter::empty()
}
}
pub fn is_closed(&self) -> bool {
self.sender.is_closed()
}
}
pub struct ShutdownAwaiter {
rx: Option<oneshot::Receiver<()>>,
done: bool,
}
impl ShutdownAwaiter {
fn new(rx: oneshot::Receiver<()>) -> Self {
Self {
rx: Some(rx),
done: false,
}
}
fn empty() -> Self {
Self {
rx: None,
done: false,
}
}
}
impl std::future::Future for ShutdownAwaiter {
type Output = ();
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let s = self.get_mut();
if let Some(rx) = &mut s.rx {
if std::pin::pin!(rx).poll(cx).is_ready() {
s.rx.take();
s.done = true;
return std::task::Poll::Ready(());
}
} else if !s.done {
s.done = true;
return std::task::Poll::Ready(());
}
std::task::Poll::Pending
}
}
impl<T> Clone for Handle<T> {
fn clone(&self) -> Self {
Handle {
service: self.service.clone(),
sender: self.sender.clone(),
}
}
}