#[cfg(all(unix, not(target_os = "macos")))] use dbus::{arg::messageitem::{MessageItem, MessageItemArray}, ffidisp::{Connection, BusType} };
#[cfg(all(unix, not(target_os = "macos")))] use crate::xdg::{build_message, NotificationHandle};
#[cfg(all(unix, not(target_os = "macos")))] use crate::hints::{Hint, message::HintMessage};
#[cfg(all(unix, not(target_os = "macos")))] use crate::urgency::Urgency;
#[cfg(all(unix, not(target_os = "macos"), feature="images"))] use crate::image::Image;
#[cfg(target_os = "windows")] use winrt_notification::Toast;
#[cfg(target_os = "windows")] use std::str::FromStr;
#[cfg(target_os = "windows")] use std::path::Path;
#[cfg(all(unix, target_os = "macos"))] use crate::macos::NotificationHandle;
use crate::timeout::Timeout;
use crate::error::*;
#[cfg(all(unix, not(target_os = "macos")))]
use std::collections::HashSet;
use std::default::Default;
use std::env;
fn exe_name() -> String {
env::current_exe().unwrap()
.file_name().unwrap().to_str().unwrap().to_owned()
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Notification {
pub appname: String,
pub summary: String,
pub subtitle: Option<String>,
pub body: String,
pub icon: String,
#[cfg(all(unix, not(target_os = "macos")))]
pub hints: HashSet<Hint>,
pub actions: Vec<String>,
#[cfg(target_os="macos")] sound_name: Option<String>,
#[cfg(target_os="windows")] sound_name: Option<String>,
#[cfg(target_os="windows")] path_to_image: Option<String>,
#[cfg(target_os="windows")] app_id: Option<String>,
pub timeout: Timeout,
pub(crate) id: Option<u32>
}
impl Notification {
pub fn new() -> Notification {
Notification::default()
}
pub fn appname(&mut self, appname: &str) -> &mut Notification {
self.appname = appname.to_owned();
self
}
pub fn summary(&mut self, summary: &str) -> &mut Notification {
self.summary = summary.to_owned();
self
}
pub fn subtitle(&mut self, subtitle: &str) -> &mut Notification {
self.subtitle = Some(subtitle.to_owned());
self
}
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
pub fn image_data(&mut self, image: Image) -> &mut Notification {
self.hint(Hint::ImageData(image));
self
}
#[cfg(all(unix, not(target_os = "macos")))]
pub fn image_path(&mut self, path: &str) -> &mut Notification {
self.hint(Hint::ImagePath(path.to_string()));
self
}
#[cfg(target_os="windows")]
pub fn image_path(&mut self, path: &str) -> &mut Notification {
self.path_to_image = Some(path.to_string());
self
}
#[cfg(target_os="windows")]
pub fn app_id(&mut self, app_id: &str) -> &mut Notification {
self.app_id = Some(app_id.to_string());
self
}
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
pub fn image<T: AsRef<std::path::Path> + Sized>(&mut self, path: T) -> Result<&mut Notification> {
let img = Image::open(&path)?;
self.hint(Hint::ImageData(img));
Ok(self)
}
#[cfg(all(unix, not(target_os = "macos")))]
pub fn sound_name(&mut self, name: &str) -> &mut Notification {
self.hint(Hint::SoundName(name.to_owned()));
self
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
pub fn sound_name(&mut self, name: &str) -> &mut Notification {
self.sound_name = Some(name.to_owned());
self
}
pub fn body(&mut self, body: &str) -> &mut Notification {
self.body = body.to_owned();
self
}
pub fn icon(&mut self, icon: &str) -> &mut Notification {
self.icon = icon.to_owned();
self
}
pub fn auto_icon(&mut self) -> &mut Notification {
self.icon = exe_name();
self
}
#[cfg(all(unix, not(target_os = "macos")))]
pub fn hint(&mut self, hint: Hint) -> &mut Notification {
self.hints.insert(hint);
self
}
pub fn timeout<T: Into<Timeout>>(&mut self, timeout: T) -> &mut Notification {
self.timeout = timeout.into();
self
}
#[cfg(all(unix, not(target_os = "macos")))]
pub fn urgency(&mut self, urgency: Urgency) -> &mut Notification {
self.hint(Hint::Urgency(urgency));
self
}
#[deprecated(note = "please use .action() only")]
pub fn actions(&mut self, actions: Vec<String>) -> &mut Notification {
self.actions = actions;
self
}
pub fn action(&mut self, identifier: &str, label: &str) -> &mut Notification {
self.actions.push(identifier.to_owned());
self.actions.push(label.to_owned());
self
}
pub fn id(&mut self, id: u32) -> &mut Notification {
self.id = Some(id);
self
}
pub fn finalize(&self) -> Notification {
self.clone()
}
#[cfg(all(unix, not(target_os = "macos")))]
fn pack_hints(&self) -> Result<MessageItem> {
if !self.hints.is_empty() {
let hints = self.hints
.iter()
.cloned()
.map(HintMessage::wrap_hint)
.collect::<Vec<(MessageItem, MessageItem)>>();
if let Ok(array) = MessageItem::new_dict(hints) {
return Ok(array);
}
}
Ok(MessageItem::Array(MessageItemArray::new(vec![], "a{sv}".into()).unwrap()))
}
#[cfg(all(unix, not(target_os = "macos")))]
fn pack_actions(&self) -> MessageItem {
if !self.actions.is_empty() {
let mut actions = vec![];
for action in &self.actions {
actions.push(action.to_owned().into());
}
if let Ok(array) = MessageItem::new_array(actions) {
return array;
}
}
MessageItem::Array(MessageItemArray::new(vec![], "as".into()).unwrap())
}
#[cfg(all(unix, not(target_os = "macos")))]
pub fn show(&self) -> Result<NotificationHandle> {
let connection = Connection::get_private(BusType::Session)?;
let inner_id = self.id.unwrap_or(0);
let id = self._show(inner_id, &connection)?;
Ok(NotificationHandle::new(id, connection, self.clone()))
}
#[cfg(target_os = "macos")]
pub fn show(&self) -> Result<NotificationHandle> {
mac_notification_sys::send_notification(
&self.summary,
&self.subtitle.as_ref().map(AsRef::as_ref),
&self.body,
&self.sound_name.as_ref().map(AsRef::as_ref)
)?;
Ok(NotificationHandle::new(self.clone()))
}
#[cfg(target_os = "windows")]
pub fn show(&self) -> Result<()> {
let sound = match &self.sound_name {
Some(chosen_sound_name) => winrt_notification::Sound::from_str(&chosen_sound_name).ok(),
None => None
};
let duration = match self.timeout {
Timeout::Default => winrt_notification::Duration::Short,
Timeout::Never => winrt_notification::Duration::Long,
Timeout::Milliseconds(t) => if t >= 25000 {
winrt_notification::Duration::Long
} else {
winrt_notification::Duration::Short
}
};
let powershell_app_id = &Toast::POWERSHELL_APP_ID.to_string();
let app_id = &self.app_id.as_ref().unwrap_or(powershell_app_id);
let mut toast = Toast::new(app_id)
.title(&self.summary)
.text1(&self.subtitle.as_ref().map(AsRef::as_ref).unwrap_or(""))
.text2(&self.body)
.sound(sound)
.duration(duration);
if let Some(image_path) = &self.path_to_image {
toast = toast.image(&Path::new(&image_path), "");
}
toast.show()
.map_err(|e| {
Error::from(ErrorKind::Msg(format!("{:?}",e)))
})
}
#[cfg(all(unix, not(target_os = "macos")))]
pub(crate) fn _show(&self, id: u32, connection: &Connection) -> Result<u32> {
let mut message = build_message("Notify");
let timeout: i32 = self.timeout.into();
message.append_items(&[self.appname.to_owned().into(),
id.into(),
self.icon.to_owned().into(),
self.summary.to_owned().into(),
self.body.to_owned().into(),
self.pack_actions(),
self.pack_hints()?,
timeout.into()
]);
let reply = connection.send_with_reply_and_block(message, 2000)?;
match reply.get_items().get(0) {
Some(&MessageItem::UInt32(ref id)) => Ok(*id),
_ => Ok(0)
}
}
#[cfg(all(unix, not(target_os = "macos")))]
pub fn show_debug(&mut self) -> Result<NotificationHandle> {
println!("Notification:\n{appname}: ({icon}) {summary:?} {body:?}\nhints: [{hints:?}]\n",
appname = self.appname,
summary = self.summary,
body = self.body,
hints = self.hints,
icon = self.icon,);
self.show()
}
}
impl Default for Notification {
#[cfg(all(unix, not(target_os = "macos")))]
fn default() -> Notification {
Notification {
appname: exe_name(),
summary: String::new(),
subtitle: None,
body: String::new(),
icon: String::new(),
hints: HashSet::new(),
actions: Vec::new(),
timeout: Timeout::Default,
id: None
}
}
#[cfg(target_os = "macos")]
fn default() -> Notification {
Notification {
appname: exe_name(),
summary: String::new(),
subtitle: None,
body: String::new(),
icon: String::new(),
actions: Vec::new(),
timeout: Timeout::Default,
sound_name: Default::default(),
id: None
}
}
#[cfg(target_os="windows")]
fn default() -> Notification {
Notification {
appname: exe_name(),
summary: String::new(),
subtitle: None,
body: String::new(),
icon: String::new(),
actions: Vec::new(),
timeout: Timeout::Default,
sound_name: Default::default(),
id: None,
path_to_image: None,
app_id: None
}
}
}