use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::time::Duration;
use crate::error::{Error, Result};
use crate::event::Event;
use crate::icon::Icon;
use crate::menu::Menu;
use crate::notification::Notification;
use crate::platform::{self, Backend, Init};
const PUMP_INTERVAL: Duration = Duration::from_millis(100);
#[derive(Clone, Debug)]
pub struct TrayConfig {
icon: Icon,
tooltip: String,
menu: Option<Menu>,
}
impl TrayConfig {
pub fn new(icon: Icon) -> TrayConfig {
TrayConfig {
icon,
tooltip: String::new(),
menu: None,
}
}
pub fn tooltip(mut self, tooltip: impl Into<String>) -> TrayConfig {
self.tooltip = tooltip.into();
self
}
pub fn menu(mut self, menu: Menu) -> TrayConfig {
self.menu = Some(menu);
self
}
}
enum Command {
SetIcon(Icon),
SetTooltip(String),
SetMenu(Option<Menu>),
Notify(Notification),
Quit,
}
pub struct Tray {
backend: Box<dyn Backend>,
tx: Sender<Command>,
rx: Receiver<Command>,
}
impl Tray {
pub fn new(config: TrayConfig) -> Result<Tray> {
let backend = platform::new_backend(Init {
icon: config.icon,
tooltip: config.tooltip,
menu: config.menu,
})?;
let (tx, rx) = mpsc::channel();
Ok(Tray { backend, tx, rx })
}
pub fn handle(&self) -> TrayHandle {
TrayHandle {
tx: self.tx.clone(),
}
}
pub fn run(self, mut callback: impl FnMut(Event)) -> Result<()> {
let Tray {
mut backend,
tx: _keep_alive,
rx,
} = self;
event_loop(backend.as_mut(), &rx, &mut callback)
}
pub fn spawn(self, mut callback: impl FnMut(Event) + Send + 'static) -> Result<TrayHandle> {
if !self.backend.can_spawn() {
return Err(Error::Unsupported);
}
let handle = self.handle();
std::thread::Builder::new()
.name("ldtray".into())
.spawn(move || {
let Tray {
mut backend,
tx: _keep_alive,
rx,
} = self;
let _ = event_loop(backend.as_mut(), &rx, &mut callback);
})
.map_err(|e| Error::Backend(format!("failed to spawn tray thread: {e}")))?;
Ok(handle)
}
}
fn event_loop(
backend: &mut dyn Backend,
rx: &Receiver<Command>,
callback: &mut dyn FnMut(Event),
) -> Result<()> {
loop {
loop {
match rx.try_recv() {
Ok(Command::SetIcon(icon)) => backend.set_icon(&icon)?,
Ok(Command::SetTooltip(text)) => backend.set_tooltip(&text)?,
Ok(Command::SetMenu(menu)) => backend.set_menu(menu.as_ref())?,
Ok(Command::Notify(notification)) => backend.notify(¬ification)?,
Ok(Command::Quit) => return Ok(()),
Err(TryRecvError::Empty) | Err(TryRecvError::Disconnected) => break,
}
}
backend.pump(PUMP_INTERVAL, callback)?;
}
}
#[derive(Clone)]
pub struct TrayHandle {
tx: Sender<Command>,
}
impl TrayHandle {
fn send(&self, command: Command) -> Result<()> {
self.tx.send(command).map_err(|_| Error::Disconnected)
}
pub fn set_icon(&self, icon: Icon) -> Result<()> {
self.send(Command::SetIcon(icon))
}
pub fn set_tooltip(&self, text: impl Into<String>) -> Result<()> {
self.send(Command::SetTooltip(text.into()))
}
pub fn set_menu(&self, menu: Menu) -> Result<()> {
self.send(Command::SetMenu(Some(menu)))
}
pub fn clear_menu(&self) -> Result<()> {
self.send(Command::SetMenu(None))
}
pub fn notify(&self, notification: Notification) -> Result<()> {
self.send(Command::Notify(notification))
}
pub fn quit(&self) -> Result<()> {
self.send(Command::Quit)
}
}