refactor(notification): pull notify_rust from crates.io (#1432)

pull/1457/head
Lucas Fernandes Nogueira 12 months ago committed by GitHub
parent 0b0088821e
commit 3779fb5063
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"notification": patch
---
Use notify_rust from crates.io instead of local fork.

34
Cargo.lock generated

@ -1812,11 +1812,8 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
@ -2711,12 +2708,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "1.3.1"
@ -3645,6 +3636,19 @@ dependencies = [
"walkdir",
]
[[package]]
name = "notify-rust"
version = "4.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5312f837191c317644f313f7b2b39f9cb1496570c74f7c17152dd3961219551f"
dependencies = [
"log",
"mac-notification-sys",
"serde",
"tauri-winrt-notification",
"zbus",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -6418,27 +6422,22 @@ dependencies = [
name = "tauri-plugin-notification"
version = "2.0.0-beta.7"
dependencies = [
"chrono",
"color-backtrace",
"ctor",
"env_logger",
"lazy_static",
"log",
"mac-notification-sys",
"maplit",
"notify-rust",
"rand 0.8.5",
"serde",
"serde_json",
"serde_repr",
"tauri",
"tauri-plugin",
"tauri-winrt-notification",
"thiserror",
"time",
"url",
"win7-notifications",
"windows-version",
"zbus",
]
[[package]]
@ -6742,12 +6741,11 @@ dependencies = [
[[package]]
name = "tauri-winrt-notification"
version = "0.4.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11a88d3c675acf67e93f3f64a1342007d52af76a0f68d02611316ccd983029bd"
checksum = "f89f5fb70d6f62381f5d9b2ba9008196150b40b75f3068eb24faeddf1c686871"
dependencies = [
"quick-xml",
"thiserror",
"windows 0.56.0",
"windows-version",
]

@ -32,18 +32,8 @@ serde_repr = "0.1"
win7-notifications = { version = "0.4.3", optional = true }
windows-version = { version = "0.1", optional = true }
[target."cfg(all(unix, not(target_os = \"macos\")))".dependencies]
lazy_static = { version = "1", optional = true }
zbus = { version = "4", optional = true }
log = "0.4"
env_logger = { version = "0.10", optional = true }
[target."cfg(target_os=\"macos\")".dependencies]
mac-notification-sys = "0.6"
chrono = { version = "0.4", optional = true }
[target."cfg(target_os=\"windows\")".dependencies]
winrt-notification = { package = "tauri-winrt-notification", version = "0.4.0" }
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
notify-rust = "4.11"
[dev-dependencies]
color-backtrace = "0.6"
@ -51,6 +41,4 @@ ctor = "0.2"
maplit = "1"
[features]
default = [ "zbus", "async" ]
async = [ ]
windows7-compat = [ "win7-notifications", "windows-version" ]

@ -160,7 +160,7 @@ mod imp {
deprecated = "This function does not work on Windows 7. Use `Self::notify` instead."
)]
pub fn show(self) -> crate::Result<()> {
let mut notification = crate::notify_rust::Notification::new();
let mut notification = notify_rust::Notification::new();
if let Some(body) = self.body {
notification.body(&body);
}
@ -186,7 +186,7 @@ mod imp {
}
#[cfg(target_os = "macos")]
{
let _ = crate::notify_rust::set_application(if tauri::is_dev() {
let _ = notify_rust::set_application(if tauri::is_dev() {
"com.apple.Terminal"
} else {
&self.identifier

@ -32,9 +32,6 @@ mod commands;
mod error;
mod models;
#[allow(dead_code, unused_imports, deprecated, clippy::derivable_impls)]
mod notify_rust;
pub use error::{Error, Result};
#[cfg(desktop)]

@ -1,161 +0,0 @@
#![allow(missing_docs)]
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
use super::image::ImageError;
use std::{fmt, num};
/// Convenient wrapper around `std::Result`.
pub type Result<T> = ::std::result::Result<T, Error>;
#[cfg(target_os = "macos")]
pub use super::macos::{ApplicationError, MacOsError, NotificationError};
/// The Error type.
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
}
/// The kind of an error.
#[derive(Debug)]
#[non_exhaustive]
pub enum ErrorKind {
/// only here for backwards compatibility
Msg(String),
#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
Dbus(dbus::Error),
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
Zbus(zbus::Error),
#[cfg(target_os = "macos")]
MacNotificationSys(mac_notification_sys::error::Error),
Parse(num::ParseIntError),
SpecVersion(String),
Conversion(String),
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
Image(ImageError),
ImplementationMissing,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.kind {
#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
ErrorKind::Dbus(ref e) => write!(f, "{}", e),
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
ErrorKind::Zbus(ref e) => write!(f, "{}", e),
#[cfg(target_os = "macos")]
ErrorKind::MacNotificationSys(ref e) => write!(f, "{}", e),
ErrorKind::Parse(ref e) => write!(f, "Parsing Error: {}", e),
ErrorKind::Conversion(ref e) => write!(f, "Conversion Error: {}", e),
ErrorKind::SpecVersion(ref e) | ErrorKind::Msg(ref e) => write!(f, "{}", e),
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
ErrorKind::Image(ref e) => write!(f, "{}", e),
ErrorKind::ImplementationMissing => write!(
f,
r#"No Dbus implementation available, please compile with either feature ="z" or feature="d""#
),
}
}
}
impl std::error::Error for Error {}
impl From<&str> for Error {
fn from(e: &str) -> Error {
Error {
kind: ErrorKind::Msg(e.into()),
}
}
}
#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
impl From<dbus::Error> for Error {
fn from(e: dbus::Error) -> Error {
Error {
kind: ErrorKind::Dbus(e),
}
}
}
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
impl From<zbus::Error> for Error {
fn from(e: zbus::Error) -> Error {
Error {
kind: ErrorKind::Zbus(e),
}
}
}
#[cfg(target_os = "macos")]
impl From<mac_notification_sys::error::Error> for Error {
fn from(e: mac_notification_sys::error::Error) -> Error {
Error {
kind: ErrorKind::MacNotificationSys(e),
}
}
}
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
impl From<ImageError> for Error {
fn from(e: ImageError) -> Error {
Error {
kind: ErrorKind::Image(e),
}
}
}
impl From<num::ParseIntError> for Error {
fn from(e: num::ParseIntError) -> Error {
Error {
kind: ErrorKind::Parse(e),
}
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error { kind }
}
}
/// Just the usual bail macro
#[macro_export]
#[doc(hidden)]
macro_rules! bail {
($e:expr) => {
return Err($e.into());
};
($fmt:expr, $($arg:tt)+) => {
return Err(format!($fmt, $($arg)+).into());
};
}
/// Exits a function early with an `Error` if the condition is not satisfied.
///
/// Similar to `assert!`, `ensure!` takes a condition and exits the function
/// if the condition fails. Unlike `assert!`, `ensure!` returns an `Error`,
/// it does not panic.
#[macro_export(local_inner_macros)]
#[doc(hidden)]
macro_rules! ensure {
($cond:expr, $e:expr) => {
if !($cond) {
bail!($e);
}
};
($cond:expr, $fmt:expr, $($arg:tt)*) => {
if !($cond) {
bail!($fmt, $($arg)*);
}
};
}

@ -1,245 +0,0 @@
#![cfg_attr(rustfmt, rustfmt_skip)]
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
use zbus::zvariant;
#[cfg(all(unix, not(target_os = "macos")))]
pub(crate) mod message;
#[cfg(all(feature = "images", any(feature = "dbus", feature = "zbus"), unix, not(target_os = "macos")))]
use super::image::Image;
#[cfg(all(feature = "images", feature = "zbus", unix, not(target_os = "macos")))]
use super::image::image_spec_str;
use super::Urgency;
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))] use super::notification::Notification;
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))] use std::collections::HashMap;
mod constants;
#[cfg(all(unix, not(target_os = "macos")))]
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
pub(crate) enum CustomHintType {
Int,
String,
}
/// `Hints` allow you to pass extra information to the server.
///
/// Many of these are standardized by either:
///
/// * <http://www.galago-project.org/specs/notification/0.9/x344.html>
/// * <https://developer.gnome.org/notification-spec/#hints>
///
/// Which of these are actually implemented depends strongly on the Notification server you talk to.
/// Usually the [`get_capabilities()`](`crate::get_capabilities`) gives some clues, but the standards usually mention much more
/// than is actually available.
///
/// you pass these to [`Notification::hint`]
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
pub enum Hint {
/// If true, server may interpret action identifiers as named icons and display those.
ActionIcons(bool),
/// Check out:
///
/// * <http://www.galago-project.org/specs/notification/0.9/x211.html>
/// * <https://developer.gnome.org/notification-spec/#categories>
Category(String),
/// Name of the `DesktopEntry` representing the calling application. In case of "firefox.desktop"
/// use "firefox". May be used to retrieve the correct icon.
DesktopEntry(String),
/// Image as raw data
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
ImageData(Image),
/// Display the image at this path.
ImagePath(String),
/// This does not work on all servers, however timeout=0 will do the job
Resident(bool),
/// Play the sound at this path.
SoundFile(String),
/// A themeable named sound from the freedesktop.org [sound naming specification](http://0pointer.de/public/sound-naming-spec.html) to play when the notification pops up. Similar to icon-name, only for sounds. An example would be "message-new-instant".
SoundName(String),
/// Suppress the notification sound.
SuppressSound(bool),
/// When set the server will treat the notification as transient and by-pass the server's persistence capability, if it should exist.
Transient(bool),
/// Lets the notification point to a certain 'x' position on the screen.
/// Requires `Y`.
X(i32),
/// Lets the notification point to a certain 'y' position on the screen.
/// Requires `X`.
Y(i32),
/// Pass me a Urgency, either Low, Normal or Critical
Urgency(Urgency),
/// If you want to pass something entirely different.
Custom(String, String),
/// A custom numerical (integer) hint
CustomInt(String, i32),
/// Only used by this `NotificationServer` implementation
Invalid // TODO find a better solution to this
}
impl Hint {
/// Get the `bool` representation of this hint.
pub fn as_bool(&self) -> Option<bool> {
match *self {
| Hint::ActionIcons(inner)
| Hint::Resident(inner)
| Hint::SuppressSound(inner)
| Hint::Transient(inner) => Some(inner),
_ => None
}
}
/// Get the `i32` representation of this hint.
pub fn as_i32(&self) -> Option<i32> {
match *self {
Hint::X(inner) | Hint::Y(inner) => Some(inner),
_ => None
}
}
/// Get the `&str` representation of this hint.
pub fn as_str(&self) -> Option<&str> {
match *self {
Hint::DesktopEntry(ref inner) |
Hint::ImagePath(ref inner) |
Hint::SoundFile(ref inner) |
Hint::SoundName(ref inner) => Some(inner),
_ => None
}
}
/// convenience converting a name and value into a hint
pub fn from_key_val(name: &str, value: &str) -> Result<Hint, String> {
match (name,value){
(constants::ACTION_ICONS,val) => val.parse::<bool>().map(Hint::ActionIcons).map_err(|e|e.to_string()),
(constants::CATEGORY, val) => Ok(Hint::Category(val.to_owned())),
(constants::DESKTOP_ENTRY, val) => Ok(Hint::DesktopEntry(val.to_owned())),
(constants::IMAGE_PATH, val) => Ok(Hint::ImagePath(val.to_owned())),
(constants::RESIDENT, val) => val.parse::<bool>().map(Hint::Resident).map_err(|e|e.to_string()),
(constants::SOUND_FILE, val) => Ok(Hint::SoundFile(val.to_owned())),
(constants::SOUND_NAME, val) => Ok(Hint::SoundName(val.to_owned())),
(constants::SUPPRESS_SOUND, val) => val.parse::<bool>().map(Hint::SuppressSound).map_err(|e|e.to_string()),
(constants::TRANSIENT, val) => val.parse::<bool>().map(Hint::Transient).map_err(|e|e.to_string()),
(constants::X, val) => val.parse::<i32>().map(Hint::X).map_err(|e|e.to_string()),
(constants::Y, val) => val.parse::<i32>().map(Hint::Y).map_err(|e|e.to_string()),
_ => Err(String::from("unknown name"))
}
}
}
#[cfg(all(unix, not(target_os = "macos")))]
impl Hint {}
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
#[test]
fn test_hints_to_map() {
// custom value should only be there once if the names are identical
let n1 = Notification::new()
.hint(Hint::Custom("foo".into(), "bar1".into()))
.hint(Hint::Custom("foo".into(), "bar2".into()))
.hint(Hint::Custom("f00".into(), "bar3".into()))
.finalize();
assert_eq!(hints_to_map(&n1), maplit::hashmap!{
"foo" => zvariant::Value::Str("bar2".into()),
"f00" => zvariant::Value::Str("bar3".into())
});
}
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
pub(crate) fn hints_to_map(notification: &Notification) -> HashMap::<&str, zvariant::Value<'_>> {
notification
.get_hints()
.map(Into::into)
.collect()
}
#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
impl<'a> From<&'a Hint> for (&'a str, zvariant::Value<'a>) {
fn from(val: &'a Hint) -> Self {
use self::constants::*;
match val {
Hint::ActionIcons(value) => (ACTION_ICONS , zvariant::Value::Bool(*value)), // bool
Hint::Category(value) => (CATEGORY , zvariant::Value::Str(value.as_str().into())),
Hint::DesktopEntry(value) => (DESKTOP_ENTRY , zvariant::Value::Str(value.as_str().into())),
#[cfg(all(feature = "zbus", feature = "images", unix, not(target_os = "macos")))]
//Hint::ImageData(image) => (image_spec(*crate::SPEC_VERSION).as_str(), ImagePayload::from(*image).into()),
Hint::ImageData(image) => (
image_spec_str(*crate::SPEC_VERSION),
zvariant::Value::Structure(
image.to_tuple().into()
)
),
Hint::ImagePath(value) => (IMAGE_PATH , zvariant::Value::Str(value.as_str().into())),
Hint::Resident(value) => (RESIDENT , zvariant::Value::Bool(*value)), // bool
Hint::SoundFile(value) => (SOUND_FILE , zvariant::Value::Str(value.as_str().into())),
Hint::SoundName(value) => (SOUND_NAME , zvariant::Value::Str(value.as_str().into())),
Hint::SuppressSound(value) => (SUPPRESS_SOUND , zvariant::Value::Bool(*value)),
Hint::Transient(value) => (TRANSIENT , zvariant::Value::Bool(*value)),
Hint::X(value) => (X , zvariant::Value::I32(*value)),
Hint::Y(value) => (Y , zvariant::Value::I32(*value)),
Hint::Urgency(value) => (URGENCY , zvariant::Value::U8(*value as u8)),
Hint::Custom(key, val) => (key.as_str() , zvariant::Value::Str(val.as_str().into())),
Hint::CustomInt(key, val) => (key.as_str() , zvariant::Value::I32(*val)),
Hint::Invalid => (INVALID , zvariant::Value::Str(INVALID.into()))
}
}
}
#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
impl<'a, A: dbus::arg::RefArg> From<(&'a String, &'a A)> for Hint {
fn from(pair: (&String, &A)) -> Self {
let (key, variant) = pair;
match (key.as_ref(), variant.as_u64(), variant.as_i64(), variant.as_str().map(String::from)) {
(constants::ACTION_ICONS, Some(1), _, _ ) => Hint::ActionIcons(true),
(constants::ACTION_ICONS, _, _, _ ) => Hint::ActionIcons(false),
(constants::URGENCY, level, _, _ ) => Hint::Urgency(level.into()),
(constants::CATEGORY, _, _, Some(name) ) => Hint::Category(name),
(constants::DESKTOP_ENTRY, _, _, Some(entry)) => Hint::DesktopEntry(entry),
(constants::IMAGE_PATH, _, _, Some(path) ) => Hint::ImagePath(path),
(constants::RESIDENT, Some(1), _, _ ) => Hint::Resident(true),
(constants::RESIDENT, _, _, _ ) => Hint::Resident(false),
(constants::SOUND_FILE, _, _, Some(path) ) => Hint::SoundFile(path),
(constants::SOUND_NAME, _, _, Some(name) ) => Hint::SoundName(name),
(constants::SUPPRESS_SOUND, Some(1), _, _ ) => Hint::SuppressSound(true),
(constants::SUPPRESS_SOUND, _, _, _ ) => Hint::SuppressSound(false),
(constants::TRANSIENT, Some(1), _, _ ) => Hint::Transient(true),
(constants::TRANSIENT, _, _, _ ) => Hint::Transient(false),
(constants::X, _, Some(x), _ ) => Hint::X(x as i32),
(constants::Y, _, Some(y), _ ) => Hint::Y(y as i32),
other => {
eprintln!("Invalid Hint {:#?} ", other);
Hint::Invalid
}
}
}
}

@ -1,16 +0,0 @@
#![allow(dead_code)]
pub const ACTION_ICONS: &str = "action-icons";
pub const CATEGORY: &str = "category";
pub const DESKTOP_ENTRY: &str = "desktop-entry";
pub const IMAGE_PATH: &str = "image-path";
pub const RESIDENT: &str = "resident";
pub const SOUND_FILE: &str = "sound-file";
pub const SOUND_NAME: &str = "sound-name";
pub const SUPPRESS_SOUND: &str = "suppress-sound";
pub const TRANSIENT: &str = "transient";
pub const X: &str = "x";
pub const Y: &str = "y";
pub const URGENCY: &str = "urgency";
pub const INVALID: &str = "invalid";

@ -1,159 +0,0 @@
//! `Hints` allow you to pass extra information to the server.
//!
//! Many of these are standardized by either:
//!
//! [galago-project spec](http://www.galago-project.org/specs/notification/0.9/x344.html) or
//! [gnome notification-spec](https://developer.gnome.org/notification-spec/#hints)
//!
//! Which of these are actually implemented depends strongly on the Notification server you talk to.
//! Usually the `get_capabilities()` gives some clues, but the standards usually mention much more
//! than is actually available.
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(dead_code, unused_imports)]
use super::{Hint, constants::*};
use super::Urgency;
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
use super::image::*;
use std::collections::{HashMap, HashSet};
#[cfg(feature = "dbus")]
use dbus::arg::{messageitem::MessageItem, RefArg};
/// All currently implemented `Hints` that can be sent.
///
/// as found on <https://developer.gnome.org/notification-spec/>
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
pub(crate) struct HintMessage(Hint);
#[cfg(feature = "dbus")]
impl HintMessage {
pub fn wrap_hint(hint: Hint) -> (MessageItem, MessageItem) {
Self::from(hint).into()
}
}
impl From<Hint> for HintMessage {
fn from(hint: Hint) -> Self {
HintMessage(hint)
}
}
impl std::ops::Deref for HintMessage {
type Target = Hint;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(feature = "dbus")]
impl<'a, A: RefArg> From<(&'a String, &'a A)> for HintMessage {
fn from(pair: (&String, &A)) -> Self {
let (key, variant) = pair;
match (key.as_ref(), variant.as_u64(), variant.as_i64(), variant.as_str().map(String::from)) {
(ACTION_ICONS, Some(1), _, _ ) => Hint::ActionIcons(true),
(ACTION_ICONS, _, _, _ ) => Hint::ActionIcons(false),
(URGENCY, level, _, _ ) => Hint::Urgency(level.into()),
(CATEGORY, _, _, Some(name) ) => Hint::Category(name),
(DESKTOP_ENTRY, _, _, Some(entry)) => Hint::DesktopEntry(entry),
(IMAGE_PATH, _, _, Some(path) ) => Hint::ImagePath(path),
(RESIDENT, Some(1), _, _ ) => Hint::Resident(true),
(RESIDENT, _, _, _ ) => Hint::Resident(false),
(SOUND_FILE, _, _, Some(path) ) => Hint::SoundFile(path),
(SOUND_NAME, _, _, Some(name) ) => Hint::SoundName(name),
(SUPPRESS_SOUND, Some(1), _, _ ) => Hint::SuppressSound(true),
(SUPPRESS_SOUND, _, _, _ ) => Hint::SuppressSound(false),
(TRANSIENT, Some(1), _, _ ) => Hint::Transient(true),
(TRANSIENT, _, _, _ ) => Hint::Transient(false),
(X, _, Some(x), _ ) => Hint::X(x as i32),
(Y, _, Some(y), _ ) => Hint::Y(y as i32),
other => {
eprintln!("Invalid Hint{:#?} ", other);
Hint::Invalid
}
}.into()
}
}
#[cfg(feature = "dbus")]
impl From<HintMessage> for (MessageItem, MessageItem) {
fn from(hint: HintMessage) -> Self {
let (key, value): (String, MessageItem) = match hint.0 {
Hint::ActionIcons(value) => (ACTION_ICONS .to_owned(), MessageItem::Bool(value)), // bool
Hint::Category(ref value) => (CATEGORY .to_owned(), MessageItem::Str(value.clone())),
Hint::DesktopEntry(ref value) => (DESKTOP_ENTRY .to_owned(), MessageItem::Str(value.clone())),
#[cfg(all(feature = "images", unix, not(target_os ="macos")))]
Hint::ImageData(image) => (image_spec(*crate::SPEC_VERSION), ImageMessage::from(image).into()),
Hint::ImagePath(ref value) => (IMAGE_PATH .to_owned(), MessageItem::Str(value.clone())),
Hint::Resident(value) => (RESIDENT .to_owned(), MessageItem::Bool(value)), // bool
Hint::SoundFile(ref value) => (SOUND_FILE .to_owned(), MessageItem::Str(value.clone())),
Hint::SoundName(ref value) => (SOUND_NAME .to_owned(), MessageItem::Str(value.clone())),
Hint::SuppressSound(value) => (SUPPRESS_SOUND .to_owned(), MessageItem::Bool(value)),
Hint::Transient(value) => (TRANSIENT .to_owned(), MessageItem::Bool(value)),
Hint::X(value) => (X .to_owned(), MessageItem::Int32(value)),
Hint::Y(value) => (Y .to_owned(), MessageItem::Int32(value)),
Hint::Urgency(value) => (URGENCY .to_owned(), MessageItem::Byte(value as u8)),
Hint::Custom(ref key, ref val) => (key .to_owned(), MessageItem::Str(val.to_owned ())),
Hint::CustomInt(ref key, val) => (key .to_owned(), MessageItem::Int32(val)),
Hint::Invalid => ("invalid" .to_owned(), MessageItem::Str("Invalid".to_owned()))
};
(MessageItem::Str(key), MessageItem::Variant(Box::new(value)))
}
}
// TODO: deprecated, Prefer the DBus Arg and RefArg APIs
#[cfg(feature = "dbus")]
impl From<(&MessageItem, &MessageItem)> for HintMessage {
fn from ((key, mut value): (&MessageItem, &MessageItem)) -> Self {
use Hint as Hint;
// If this is a variant, consider the thing inside it
// If it's a nested variant, keep drilling down until we get a real value
while let MessageItem::Variant(inner) = value {
value = inner;
}
let is_stringy = value.inner::<&str>().is_ok();
match key.inner::<&str>() {
Ok(CATEGORY) => value.inner::<&str>().map(String::from).map(Hint::Category),
Ok(ACTION_ICONS) => value.inner().map(Hint::ActionIcons),
Ok(DESKTOP_ENTRY) => value.inner::<&str>().map(String::from).map(Hint::DesktopEntry),
Ok(IMAGE_PATH) => value.inner::<&str>().map(String::from).map(Hint::ImagePath),
Ok(RESIDENT) => value.inner().map(Hint::Resident),
Ok(SOUND_FILE) => value.inner::<&str>().map(String::from).map(Hint::SoundFile),
Ok(SOUND_NAME) => value.inner::<&str>().map(String::from).map(Hint::SoundName),
Ok(SUPPRESS_SOUND) => value.inner().map(Hint::SuppressSound),
Ok(TRANSIENT) => value.inner().map(Hint::Transient),
Ok(X) => value.inner().map(Hint::X),
Ok(Y) => value.inner().map(Hint::Y),
Ok(URGENCY) => value.inner().map(|i| match i {
0 => Urgency::Low,
2 => Urgency::Critical,
_ => Urgency::Normal
}).map(Hint::Urgency),
Ok(k) if is_stringy => value.inner::<&str>().map(|v| Hint::Custom(k.to_string(), v.to_string())),
Ok(k) => value.inner().map(|v| Hint::CustomInt(k.to_string(), v)),
_ => Err(()),
}.unwrap_or(Hint::Invalid)
.into()
}
}
#[allow(missing_docs)]
#[cfg(feature = "dbus")]
pub(crate) fn hints_from_variants<A: RefArg>(hints: &HashMap<String, A>) -> HashSet<HintMessage> {
hints.iter().map(Into::into).collect()
}

@ -1,86 +0,0 @@
#![cfg(all(test, unix, not(target_os = "macos")))]
use ctor::ctor;
use dbus::arg::messageitem::MessageItem as Item;
use self::Hint;
use super::Urgency::*;
use super::*;
#[ctor]
fn init_color_backtrace() {
color_backtrace::install();
}
#[test]
fn hint_to_item() {
let category = &Hint::Category("test-me".to_owned());
let (k, v) = category.into();
let test_k = Item::Str("category".into());
let test_v = Item::Variant(Box::new(Item::Str("test-me".into())));
assert_eq!(k, test_k);
assert_eq!(v, test_v);
}
#[test]
fn urgency() {
let low = &Hint::Urgency(Low);
let (k, v) = low.into();
let test_k = Item::Str("urgency".into());
let test_v = Item::Variant(Box::new(Item::Byte(0)));
assert_eq!(k, test_k);
assert_eq!(v, test_v);
}
#[test]
fn simple_hint_to_item() {
let old_hint = &Hint::Custom("foo".into(), "bar".into());
let (k, v) = old_hint.into();
let hint: Hint = (&k, &v).into();
assert_eq!(old_hint, &hint);
}
#[test]
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
fn imagedata_hint_to_item() {
let hint = &Hint::ImageData(Image::from_rgb(1, 1, vec![0, 0, 0]).unwrap());
let item: MessageItem = hint.into();
let test_item = Item::DictEntry(
Box::new(Item::Str(image_spec(*::SPEC_VERSION))),
Box::new(Item::Variant(Box::new(Item::Struct(vec![
Item::Int32(1),
Item::Int32(1),
Item::Int32(3),
Item::Bool(false),
Item::Int32(8),
Item::Int32(3),
Item::Array(
dbus::MessageItemArray::new(
vec![Item::Byte(0), Item::Byte(0), Item::Byte(0)],
"ay".into(),
)
.unwrap(),
),
])))),
);
assert_eq!(item, test_item);
}
#[test]
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
fn imagedata_hint_to_item_with_spec() {
let key = image_spec(Version::new(1, 0));
assert_eq!(key, String::from("icon_data"));
let key = image_spec(Version::new(1, 1));
assert_eq!(key, String::from("image_data"));
let key = image_spec(Version::new(1, 2));
assert_eq!(key, String::from("image-data"));
}

@ -1,229 +0,0 @@
#[cfg(feature = "dbus")]
use dbus::arg::messageitem::{MessageItem, MessageItemArray};
pub use image::DynamicImage;
use std::cmp::Ordering;
use std::convert::TryFrom;
use std::error::Error;
use std::fmt;
use std::path::Path;
use super::miniver::Version;
mod constants {
pub const IMAGE_DATA: &str = "image-data";
pub const IMAGE_DATA_1_1: &str = "image_data";
pub const IMAGE_DATA_1_0: &str = "icon_data";
}
/// Image representation for images. Send via `Notification::image_data()`
#[derive(PartialEq, Eq, Debug, Clone, Hash)]
pub struct Image {
width: i32,
height: i32,
rowstride: i32,
alpha: bool,
bits_per_sample: i32,
channels: i32,
data: Vec<u8>,
}
impl Image {
fn from_raw_data(
width: i32,
height: i32,
data: Vec<u8>,
channels: i32,
bits_per_sample: i32,
alpha: bool,
) -> Result<Self, ImageError> {
const MAX_SIZE: i32 = 0x0fff_ffff;
if width > MAX_SIZE || height > MAX_SIZE {
return Err(ImageError::TooBig);
}
if data.len() != (width * height * channels) as usize {
Err(ImageError::WrongDataSize)
} else {
Ok(Self {
width,
height,
bits_per_sample,
channels,
data,
rowstride: width * channels,
alpha,
})
}
}
/// Creates an image from a raw vector of bytes
pub fn from_rgb(width: i32, height: i32, data: Vec<u8>) -> Result<Self, ImageError> {
let channels = 3i32;
let bits_per_sample = 8;
Self::from_raw_data(width, height, data, channels, bits_per_sample, false)
}
/// Creates an image from a raw vector of bytes with alpha
pub fn from_rgba(width: i32, height: i32, data: Vec<u8>) -> Result<Self, ImageError> {
let channels = 4i32;
let bits_per_sample = 8;
Self::from_raw_data(width, height, data, channels, bits_per_sample, true)
}
/// Attempts to open the given path as image
pub fn open<T: AsRef<Path> + Sized>(path: T) -> Result<Self, ImageError> {
let dyn_img = image::open(&path).map_err(ImageError::CantOpen)?;
Image::try_from(dyn_img)
}
#[cfg(all(feature = "images", feature = "zbus"))]
pub(crate) fn to_tuple(&self) -> (i32, i32, i32, bool, i32, i32, Vec<u8>) {
(
self.width,
self.height,
self.rowstride,
self.alpha,
self.bits_per_sample,
self.channels,
self.data.clone(),
)
}
}
impl TryFrom<DynamicImage> for Image {
type Error = ImageError;
fn try_from(dyn_img: DynamicImage) -> Result<Self, Self::Error> {
match dyn_img {
DynamicImage::ImageRgb8(img) => Self::try_from(img),
DynamicImage::ImageRgba8(img) => Self::try_from(img),
_ => Err(ImageError::CantConvert),
}
}
}
impl TryFrom<image::RgbImage> for Image {
type Error = ImageError;
fn try_from(img: image::RgbImage) -> Result<Self, Self::Error> {
let (width, height) = img.dimensions();
let image_data = img.into_raw();
Image::from_rgb(width as i32, height as i32, image_data)
}
}
impl TryFrom<image::RgbaImage> for Image {
type Error = ImageError;
fn try_from(img: image::RgbaImage) -> Result<Self, Self::Error> {
let (width, height) = img.dimensions();
let image_data = img.into_raw();
Image::from_rgba(width as i32, height as i32, image_data)
}
}
/// Errors that can occur when creating an Image
#[derive(Debug)]
pub enum ImageError {
/// The given image is too big. DBus only has 32 bits for width / height
TooBig,
/// The given bytes don't match the width, height and channel count
WrongDataSize,
/// Can't open given path
CantOpen(image::ImageError),
/// Can't convert from given input
CantConvert,
}
impl Error for ImageError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use ImageError::*;
match self {
TooBig | WrongDataSize | CantConvert => None,
CantOpen(e) => Some(e),
}
}
}
impl fmt::Display for ImageError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ImageError::*;
match self {
TooBig => writeln!(
f,
"The given image is too big. DBus only has 32 bits for width / height"
),
WrongDataSize => writeln!(
f,
"The given bytes don't match the width, height and channel count"
),
CantOpen(e) => writeln!(f, "Can't open given path {}", e),
CantConvert => writeln!(f, "Can't convert from given input"),
}
}
}
/// matching image data key for each spec version
#[cfg(feature = "dbus")]
pub(crate) fn image_spec(version: Version) -> String {
match version.cmp(&Version::new(1, 1)) {
Ordering::Less => constants::IMAGE_DATA_1_0.to_owned(),
Ordering::Equal => constants::IMAGE_DATA_1_1.to_owned(),
Ordering::Greater => constants::IMAGE_DATA.to_owned(),
}
}
/// matching image data key for each spec version
#[cfg(feature = "zbus")]
pub(crate) fn image_spec_str(version: Version) -> &'static str {
match version.cmp(&Version::new(1, 1)) {
Ordering::Less => constants::IMAGE_DATA_1_0,
Ordering::Equal => constants::IMAGE_DATA_1_1,
Ordering::Greater => constants::IMAGE_DATA,
}
}
#[cfg(feature = "dbus")]
pub struct ImageMessage(Image);
#[cfg(feature = "dbus")]
impl From<Image> for ImageMessage {
fn from(hint: Image) -> Self {
ImageMessage(hint)
}
}
impl From<image::ImageError> for ImageError {
fn from(image_error: image::ImageError) -> Self {
ImageError::CantOpen(image_error)
}
}
#[cfg(feature = "dbus")]
impl std::ops::Deref for ImageMessage {
type Target = Image;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(feature = "dbus")]
impl From<ImageMessage> for MessageItem {
fn from(img_msg: ImageMessage) -> Self {
let img = img_msg.0;
let bytes = img.data.into_iter().map(MessageItem::Byte).collect();
MessageItem::Struct(vec![
MessageItem::Int32(img.width),
MessageItem::Int32(img.height),
MessageItem::Int32(img.rowstride),
MessageItem::Bool(img.alpha),
MessageItem::Int32(img.bits_per_sample),
MessageItem::Int32(img.channels),
MessageItem::Array(MessageItemArray::new(bytes, "ay".into()).unwrap()),
])
}
}

@ -1,61 +0,0 @@
use super::{error::*, notification::Notification};
pub use mac_notification_sys::error::{ApplicationError, Error as MacOsError, NotificationError};
use std::ops::{Deref, DerefMut};
/// A handle to a shown notification.
///
/// This keeps a connection alive to ensure actions work on certain desktops.
#[derive(Debug)]
pub struct NotificationHandle {
notification: Notification,
}
impl NotificationHandle {
#[allow(missing_docs)]
pub fn new(notification: Notification) -> NotificationHandle {
NotificationHandle { notification }
}
}
impl Deref for NotificationHandle {
type Target = Notification;
fn deref(&self) -> &Notification {
&self.notification
}
}
/// Allow to easily modify notification properties
impl DerefMut for NotificationHandle {
fn deref_mut(&mut self) -> &mut Notification {
&mut self.notification
}
}
pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
mac_notification_sys::Notification::default()
.title(notification.summary.as_str())
.message(&notification.body)
.maybe_subtitle(notification.subtitle.as_deref())
.maybe_sound(notification.sound_name.as_deref())
.send()?;
Ok(NotificationHandle::new(notification.clone()))
}
pub(crate) fn schedule_notification(
notification: &Notification,
delivery_date: f64,
) -> Result<NotificationHandle> {
mac_notification_sys::Notification::default()
.title(notification.summary.as_str())
.message(&notification.body)
.maybe_subtitle(notification.subtitle.as_deref())
.maybe_sound(notification.sound_name.as_deref())
.delivery_date(delivery_date)
.send()?;
Ok(NotificationHandle::new(notification.clone()))
}

@ -1,75 +0,0 @@
use super::error::*;
use std::str::FromStr;
#[derive(Copy, Clone, Eq, Debug)]
pub struct Version {
pub major: u64,
pub minor: u64,
}
impl Version {
#[allow(dead_code)]
pub fn new(major: u64, minor: u64) -> Self {
Self { major, minor }
}
}
impl FromStr for Version {
type Err = Error;
fn from_str(s: &str) -> Result<Version> {
let vv = s.split('.').collect::<Vec<&str>>();
match (vv.first(), vv.get(1)) {
(Some(maj), Some(min)) => Ok(Version {
major: maj.parse()?,
minor: min.parse()?,
}),
_ => Err(ErrorKind::SpecVersion(s.into()).into()),
}
}
}
use std::cmp;
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Version) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Version {
fn eq(&self, other: &Version) -> bool {
self.major == other.major && self.minor == other.minor
}
}
impl Ord for Version {
fn cmp(&self, other: &Version) -> cmp::Ordering {
match self.major.cmp(&other.major) {
cmp::Ordering::Equal => {}
r => return r,
}
match self.minor.cmp(&other.minor) {
cmp::Ordering::Equal => {}
r => return r,
}
cmp::Ordering::Equal
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_parsing() {
assert_eq!("1.3".parse::<Version>().unwrap(), Version::new(1, 3));
}
#[test]
fn version_comparison() {
assert!(Version::new(1, 3) >= Version::new(1, 2));
assert!(Version::new(1, 2) >= Version::new(1, 2));
assert!(Version::new(1, 2) == Version::new(1, 2));
assert!(Version::new(1, 1) <= Version::new(1, 2));
}
}

@ -1,173 +0,0 @@
//! Desktop Notifications for Rust.
//!
//! Desktop notifications are popup messages generated to notify the user of certain events.
//!
//! ## Platform Support
//!
//! This library was originally conceived with the [XDG](https://en.wikipedia.org/wiki/XDG) notification specification in mind.
//! Since version 3.3 this crate also builds on macOS, however the semantics of the [XDG](https://en.wikipedia.org/wiki/XDG) specification and macOS `NSNotifications`
//! are quite different.
//! Therefore only a very small subset of functions is supported on macOS.
//! Certain methods don't have any effect there, others will explicitly fail to compile,
//! in these cases you will have to add platform specific toggles to your code.
//! For more see [platform differences](#platform-differences)
//!
//! # Platform Differences
//! <details>
//! ✔︎ = works <br/>
//! ❌ = will not compile
//!
//! ## `Notification`
//! | method | XDG | macOS | windows |
//! |---------------------|-------|-------|---------|
//! | `fn appname(...)` | ✔︎ | | |
//! | `fn summary(...)` | ✔︎ | ✔︎ | ✔︎ |
//! | `fn subtitle(...)` | | ✔︎ | ✔︎ |
//! | `fn body(...)` | ✔︎ | ✔︎ | ✔︎ |
//! | `fn icon(...)` | ✔︎ | | |
//! | `fn auto_icon(...)`| ✔︎ | | |
//! | `fn hint(...)` | ✔︎ | ❌ | ❌ |
//! | `fn timeout(...)` | ✔︎ | | ✔︎ |
//! | `fn urgency(...)` | ✔︎ | ❌ | ❌ |
//! | `fn action(...)` | ✔︎ | | |
//! | `fn id(...)` | ✔︎ | | |
//! | `fn finalize(...)` | ✔︎ | ✔︎ | ✔︎ |
//! | `fn show(...)` | ✔︎ | ✔︎ | ✔︎ |
//!
//! ## `NotificationHandle`
//!
//! | method | XDG | macOS | windows |
//! |--------------------------|-----|-------|---------|
//! | `fn wait_for_action(...)`| ✔︎ | ❌ | ❌ |
//! | `fn close(...)` | ✔︎ | ❌ | ❌ |
//! | `fn on_close(...)` | ✔︎ | ❌ | ❌ |
//! | `fn update(...)` | ✔︎ | ❌ | ❌ |
//! | `fn id(...)` | ✔︎ | ❌ | ❌ |
//!
//! ## Functions
//!
//! | | XDG | macOS | windows |
//! |--------------------------------------------|-----|-------|---------|
//! | `fn get_capabilities(...)` | ✔︎ | ❌ | ❌ |
//! | `fn get_server_information(...)` | ✔︎ | ❌ | ❌ |
//! | `fn set_application(...)` | ❌ | ✔︎ | ❌ |
//! | `fn get_bundle_identifier_or_default(...)` | ❌ | ✔︎ | ❌ |
//!
//!
//! ### Toggles
//!
//! Please use `target_os` toggles if you plan on using methods labeled with ❌.
//!
//! ```ignore
//! #[cfg(target_os = "macos")]
//! // or
//! // #### #[cfg(all(unix, not(target_os = "macos")))]
//! ```
//! </details>
//!
#![deny(
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unused_import_braces,
unused_qualifications
)]
#![warn(
missing_docs,
clippy::doc_markdown,
clippy::semicolon_if_nothing_returned,
clippy::single_match_else,
clippy::inconsistent_struct_constructor,
clippy::map_unwrap_or,
clippy::match_same_arms
)]
#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
extern crate dbus;
#[cfg(target_os = "macos")]
extern crate mac_notification_sys;
#[cfg(target_os = "windows")]
extern crate winrt_notification;
#[macro_use]
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
extern crate lazy_static;
pub mod error;
mod hints;
mod miniver;
mod notification;
mod timeout;
pub(crate) mod urgency;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(all(unix, not(target_os = "macos")))]
mod xdg;
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
mod image;
#[cfg(all(feature = "server", feature = "dbus", unix, not(target_os = "macos")))]
pub mod server;
#[cfg(target_os = "macos")]
pub use mac_notification_sys::{get_bundle_identifier_or_default, set_application};
#[cfg(target_os = "macos")]
pub use macos::NotificationHandle;
#[cfg(all(
any(feature = "dbus", feature = "zbus"),
unix,
not(target_os = "macos")
))]
pub use xdg::{
dbus_stack, get_capabilities, get_server_information, handle_action, ActionResponse,
CloseHandler, CloseReason, DbusStack, NotificationHandle,
};
#[cfg(all(feature = "server", unix, not(target_os = "macos")))]
pub use xdg::stop_server;
pub use hints::Hint;
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
pub use image::{Image, ImageError};
#[cfg_attr(
target_os = "macos",
deprecated(note = "Urgency is not supported on macOS")
)]
pub use urgency::Urgency;
pub use {notification::Notification, timeout::Timeout};
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
lazy_static! {
/// Read once at runtime. Needed for Images
pub static ref SPEC_VERSION: miniver::Version =
get_server_information()
.and_then(|info| info.spec_version.parse::<miniver::Version>())
.unwrap_or_else(|_| miniver::Version::new(1,1));
}
/// Return value of `get_server_information()`.
#[derive(Debug)]
pub struct ServerInformation {
/// The product name of the server.
pub name: String,
/// The vendor name.
pub vendor: String,
/// The server's version string.
pub version: String,
/// The specification version the server is compliant with.
pub spec_version: String,
}

@ -1,480 +0,0 @@
#[cfg(all(unix, not(target_os = "macos")))]
use super::{
hints::{CustomHintType, Hint},
urgency::Urgency,
xdg,
};
#[cfg(all(unix, not(target_os = "macos"), feature = "images"))]
use super::image::Image;
#[cfg(all(unix, target_os = "macos"))]
use super::macos;
#[cfg(target_os = "windows")]
use super::windows;
use super::{error::*, timeout::Timeout};
#[cfg(all(unix, not(target_os = "macos")))]
use std::collections::{HashMap, HashSet};
// Returns the name of the current executable, used as a default for `Notification.appname`.
fn exe_name() -> String {
std::env::current_exe()
.unwrap()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_owned()
}
/// Desktop notification.
///
/// A desktop notification is configured via builder pattern, before it is launched with `show()`.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Notification {
/// Filled by default with executable name.
pub appname: String,
/// Single line to summarize the content.
pub summary: String,
/// Subtitle for macOS
pub subtitle: Option<String>,
/// Multiple lines possible, may support simple markup,
/// check out `get_capabilities()` -> `body-markup` and `body-hyperlinks`.
pub body: String,
/// Use a file:// URI or a name in an icon theme, must be compliant freedesktop.org.
pub icon: String,
/// Check out `Hint`
///
/// # warning
/// this does not hold all hints, [`Hint::Custom`] and [`Hint::CustomInt`] are held elsewhere,
// /// please access hints via [`Notification::get_hints`].
#[cfg(all(unix, not(target_os = "macos")))]
pub hints: HashSet<Hint>,
#[cfg(all(unix, not(target_os = "macos")))]
pub(crate) hints_unique: HashMap<(String, CustomHintType), Hint>,
/// See `Notification::actions()` and `Notification::action()`
pub actions: Vec<String>,
#[cfg(target_os = "macos")]
pub(crate) sound_name: Option<String>,
#[cfg(target_os = "windows")]
pub(crate) sound_name: Option<String>,
#[cfg(target_os = "windows")]
pub(crate) path_to_image: Option<String>,
#[cfg(target_os = "windows")]
pub(crate) app_id: Option<String>,
#[cfg(all(unix, not(target_os = "macos")))]
pub(crate) bus: xdg::NotificationBus,
/// Lifetime of the Notification in ms. Often not respected by server, sorry.
pub timeout: Timeout, // both gnome and galago want allow for -1
/// Only to be used on the receive end. Use Notification hand for updating.
pub(crate) id: Option<u32>,
}
impl Notification {
/// Constructs a new Notification.
///
/// Most fields are empty by default, only `appname` is initialized with the name of the current
/// executable.
/// The appname is used by some desktop environments to group notifications.
pub fn new() -> Notification {
Notification::default()
}
/// This is for testing purposes only and will not work with actual implementations.
#[cfg(all(unix, not(target_os = "macos")))]
#[doc(hidden)]
#[deprecated(note = "this is a test only feature")]
pub fn at_bus(sub_bus: &str) -> Notification {
let bus = xdg::NotificationBus::custom(sub_bus)
.ok_or("invalid subpath")
.unwrap();
Notification {
bus,
..Notification::default()
}
}
/// Overwrite the appname field used for Notification.
///
/// # Platform Support
/// Please note that this method has no effect on macOS. Here you can only set the application via [`set_application()`](fn.set_application.html)
pub fn appname(&mut self, appname: &str) -> &mut Notification {
appname.clone_into(&mut self.appname);
self
}
/// Set the `summary`.
///
/// Often acts as title of the notification. For more elaborate content use the `body` field.
pub fn summary(&mut self, summary: &str) -> &mut Notification {
summary.clone_into(&mut self.summary);
self
}
/// Set the `subtitle`.
///
/// This is only useful on macOS, it's not part of the XDG specification and will therefore be eaten by gremlins under your CPU 😈🤘.
pub fn subtitle(&mut self, subtitle: &str) -> &mut Notification {
self.subtitle = Some(subtitle.to_owned());
self
}
/// Manual wrapper for `Hint::ImageData`
#[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
}
/// Wrapper for `Hint::ImagePath`
#[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
}
/// Wrapper for `NotificationHint::ImagePath`
#[cfg(target_os = "windows")]
pub fn image_path(&mut self, path: &str) -> &mut Notification {
self.path_to_image = Some(path.to_string());
self
}
/// app's System.AppUserModel.ID
#[cfg(target_os = "windows")]
pub fn app_id(&mut self, app_id: &str) -> &mut Notification {
self.app_id = Some(app_id.to_string());
self
}
/// Wrapper for `Hint::ImageData`
#[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)
}
/// Wrapper for `Hint::SoundName`
#[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
}
/// Set the `sound_name` for the `NSUserNotification`
#[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
}
/// Set the content of the `body` field.
///
/// Multiline textual content of the notification.
/// Each line should be treated as a paragraph.
/// Simple html markup should be supported, depending on the server implementation.
pub fn body(&mut self, body: &str) -> &mut Notification {
body.clone_into(&mut self.body);
self
}
/// Set the `icon` field.
///
/// You can use common icon names here, usually those in `/usr/share/icons`
/// can all be used.
/// You can also use an absolute path to file.
///
/// # Platform support
/// macOS does not have support manually setting the icon. However you can pretend to be another app using [`set_application()`](fn.set_application.html)
pub fn icon(&mut self, icon: &str) -> &mut Notification {
icon.clone_into(&mut self.icon);
self
}
/// Set the `icon` field automatically.
///
/// This looks at your binary's name and uses it to set the icon.
///
/// # Platform support
/// macOS does not support manually setting the icon. However you can pretend to be another app using [`set_application()`](fn.set_application.html)
pub fn auto_icon(&mut self) -> &mut Notification {
self.icon = exe_name();
self
}
/// Adds a hint.
///
/// This method will add a hint to the internal hint [`HashSet`].
/// Hints must be of type [`Hint`].
///
/// Many of these are again wrapped by more convenient functions such as:
///
/// * `sound_name(...)`
/// * `urgency(...)`
/// * [`image(...)`](#method.image) or
/// * [`image_data(...)`](#method.image_data)
/// * [`image_path(...)`](#method.image_path)
///
/// # Platform support
/// Most of these hints don't even have an effect on the big XDG Desktops, they are completely tossed on macOS.
#[cfg(all(unix, not(target_os = "macos")))]
pub fn hint(&mut self, hint: Hint) -> &mut Notification {
match hint {
Hint::CustomInt(k, v) => {
self.hints_unique
.insert((k.clone(), CustomHintType::Int), Hint::CustomInt(k, v));
}
Hint::Custom(k, v) => {
self.hints_unique
.insert((k.clone(), CustomHintType::String), Hint::Custom(k, v));
}
_ => {
self.hints.insert(hint);
}
}
self
}
#[cfg(all(unix, not(target_os = "macos")))]
pub(crate) fn get_hints(&self) -> impl Iterator<Item = &Hint> {
self.hints.iter().chain(self.hints_unique.values())
}
/// Set the `timeout`.
///
/// Accepts multiple types that implement `Into<Timeout>`.
///
/// ## `i31`
///
/// This sets the time (in milliseconds) from the time the notification is displayed until it is
/// closed again by the Notification Server.
/// According to [specification](https://developer.gnome.org/notification-spec/)
/// -1 will leave the timeout to be set by the server and
/// 0 will cause the notification never to expire.
/// ## [Duration](`std::time::Duration`)
///
/// When passing a [`Duration`](`std::time::Duration`) we will try convert it into milliseconds.
///
/// # Platform support
/// This only works on XDG Desktops, macOS does not support manually setting the timeout.
pub fn timeout<T: Into<Timeout>>(&mut self, timeout: T) -> &mut Notification {
self.timeout = timeout.into();
self
}
/// Set the `urgency`.
///
/// Pick between Medium, Low and High.
///
/// # Platform support
/// Most Desktops on linux and bsd are far too relaxed to pay any attention to this.
/// In macOS this does not exist
#[cfg(all(unix, not(target_os = "macos")))]
pub fn urgency(&mut self, urgency: Urgency) -> &mut Notification {
self.hint(Hint::Urgency(urgency)); // TODO impl as T where T: Into<Urgency>
self
}
/// Set `actions`.
///
/// To quote <http://www.galago-project.org/specs/notification/0.9/x408.html#command-notify>
///
/// > Actions are sent over as a list of pairs.
/// > Each even element in the list (starting at index 0) represents the identifier for the action.
/// > Each odd element in the list is the localized string that will be displayed to the user.y
///
/// There is nothing fancy going on here yet.
/// **Careful! This replaces the internal list of actions!**
///
/// (xdg only)
#[deprecated(note = "please use .action() only")]
pub fn actions(&mut self, actions: Vec<String>) -> &mut Notification {
self.actions = actions;
self
}
/// Add an action.
///
/// This adds a single action to the internal list of actions.
///
/// (xdg only)
pub fn action(&mut self, identifier: &str, label: &str) -> &mut Notification {
self.actions.push(identifier.to_owned());
self.actions.push(label.to_owned());
self
}
/// Set an Id ahead of time
///
/// Setting the id ahead of time allows overriding a known other notification.
/// Though if you want to update a notification, it is easier to use the `update()` method of
/// the `NotificationHandle` object that `show()` returns.
///
/// (xdg only)
pub fn id(&mut self, id: u32) -> &mut Notification {
self.id = Some(id);
self
}
/// Finalizes a Notification.
///
/// Part of the builder pattern, returns a complete copy of the built notification.
pub fn finalize(&self) -> Notification {
self.clone()
}
/// Schedules a Notification
///
/// Sends a Notification at the specified date.
#[cfg(all(target_os = "macos", feature = "chrono"))]
pub fn schedule<T: chrono::TimeZone>(
&self,
delivery_date: chrono::DateTime<T>,
) -> Result<macos::NotificationHandle> {
macos::schedule_notification(self, delivery_date.timestamp() as f64)
}
/// Schedules a Notification
///
/// Sends a Notification at the specified timestamp.
/// This is a raw `f64`, if that is a bit too raw for you please activate the feature `"chrono"`,
/// then you can use `Notification::schedule()` instead, which accepts a `chrono::DateTime<T>`.
#[cfg(target_os = "macos")]
pub fn schedule_raw(&self, timestamp: f64) -> Result<macos::NotificationHandle> {
macos::schedule_notification(self, timestamp)
}
/// Sends Notification to D-Bus.
///
/// Returns a handle to a notification
#[cfg(all(unix, not(target_os = "macos")))]
pub fn show(&self) -> Result<xdg::NotificationHandle> {
xdg::show_notification(self)
}
/// Sends Notification to D-Bus.
///
/// Returns a handle to a notification
#[cfg(all(unix, not(target_os = "macos")))]
#[cfg(all(feature = "async", feature = "zbus"))]
pub async fn show_async(&self) -> Result<xdg::NotificationHandle> {
xdg::show_notification_async(self).await
}
/// Sends Notification to D-Bus.
///
/// Returns a handle to a notification
#[cfg(all(unix, not(target_os = "macos")))]
#[cfg(feature = "async")]
// #[cfg(test)]
pub async fn show_async_at_bus(&self, sub_bus: &str) -> Result<xdg::NotificationHandle> {
let bus = xdg::NotificationBus::custom(sub_bus).ok_or("invalid subpath")?;
xdg::show_notification_async_at_bus(self, bus).await
}
/// Sends Notification to `NSUserNotificationCenter`.
///
/// Returns an `Ok` no matter what, since there is currently no way of telling the success of
/// the notification.
#[cfg(target_os = "macos")]
pub fn show(&self) -> Result<macos::NotificationHandle> {
macos::show_notification(self)
}
/// Sends Notification to `NSUserNotificationCenter`.
///
/// Returns an `Ok` no matter what, since there is currently no way of telling the success of
/// the notification.
#[cfg(target_os = "windows")]
pub fn show(&self) -> Result<()> {
windows::show_notification(self)
}
/// Wraps `show()` but prints notification to stdout.
#[cfg(all(unix, not(target_os = "macos")))]
#[deprecated = "this was never meant to be public API"]
pub fn show_debug(&mut self) -> Result<xdg::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(),
hints_unique: HashMap::new(),
actions: Vec::new(),
timeout: Timeout::Default,
bus: Default::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,
}
}
}

@ -1,238 +0,0 @@
//! **Experimental** server taking the place of your Desktop Environment's Notification Server.
//!
//! This is not nearly meant for anything but testing, as it only prints notifications to stdout.
//! It does not respond properly either yet.
//!
//! This server will not replace an already running notification server.
//!
#![allow(unused_imports, unused_variables, dead_code)]
use std::cell::Cell;
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
#[cfg(feature = "dbus")]
use dbus::{
arg::{self, RefArg},
ffidisp::{BusType, Connection, NameFlag},
tree::{self, Factory, Interface, MTFn, MTSync, Tree},
Path,
};
use super::xdg::{NOTIFICATION_NAMESPACE, NOTIFICATION_OBJECTPATH};
use super::{Hint, Notification, Timeout};
static DBUS_ERROR_FAILED: &str = "org.freedesktop.DBus.Error.Failed";
/// Version of the crate equals the version server.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// An **experimental** notification server.
/// See [the module level documentation](index.html) for more.
#[derive(Debug, Default)]
pub struct NotificationServer {
/// Counter for generating notification ids
counter: Mutex<Cell<u32>>,
/// A flag that stops the server
stopped: Mutex<Cell<bool>>,
}
impl NotificationServer {
fn count_up(&self) {
if let Ok(counter) = self.counter.lock() {
counter.set(counter.get() + 1);
}
}
fn stop(&self) {
if let Ok(stop) = self.stopped.lock() {
stop.set(true);
}
}
fn is_stopped(&self) -> bool {
if let Ok(stop) = self.stopped.lock() {
stop.get()
} else {
true
}
}
/// Create a new `NotificationServer` instance.
pub fn create() -> Arc<NotificationServer> {
Arc::new(NotificationServer::default())
}
// pub fn notify_mothod<F>(&mut self, closure: F)
// -> Method
// where F: Fn(&Notification)
// {
// fn handle_notification
/// Start listening for incoming notifications
pub fn start<F: 'static>(me: &Arc<Self>, closure: F)
where
F: Fn(&Notification),
{
let connection = Connection::get_private(BusType::Session).unwrap();
connection.release_name(NOTIFICATION_NAMESPACE).unwrap();
connection
.register_name(NOTIFICATION_NAMESPACE, NameFlag::ReplaceExisting as u32)
.unwrap();
connection
.register_object_path(NOTIFICATION_OBJECTPATH)
.unwrap();
let mytex = Arc::new(Mutex::new(me.clone()));
let factory = Factory::new_fn::<()>(); // D::Tree = ()
let tree = factory.tree(()).add(
factory
.object_path(NOTIFICATION_OBJECTPATH, ())
.introspectable()
.add(
factory
.interface(NOTIFICATION_NAMESPACE, ())
.add_m(method_notify(&factory, closure))
.add_m(method_close_notification(&factory))
.add_m(Self::stop_server(mytex.clone(), &factory))
// .add_signal(method_notification_closed(&factory))
// .add_signal(method_action_invoked(&factory))
.add_m(method_get_capabilities(&factory))
.add_m(method_get_server_information(&factory)),
),
);
connection.add_handler(tree);
while !me.is_stopped() {
// Wait for incoming messages. This will block up to one second.
// Discard the result - relevant messages have already been handled.
if let Some(received) = connection.incoming(1000).next() {
println!("RECEIVED {:?}", received);
}
}
}
fn stop_server(
me: Arc<Mutex<Arc<Self>>>,
factory: &Factory<MTFn>,
) -> tree::Method<MTFn<()>, ()> {
factory
.method("Stop", (), move |minfo| {
if let Ok(me) = me.lock() {
me.stop();
println!("STOPPING");
Ok(vec![])
} else {
Err(tree::MethodErr::failed(&String::from("nope!")))
}
})
.out_arg(("", "u"))
}
}
fn hints_from_variants<A: RefArg>(hints: &HashMap<String, A>) -> HashSet<Hint> {
hints.iter().map(Into::into).collect()
}
fn method_notify<F: 'static>(
factory: &Factory<MTFn>,
on_notification: F,
) -> tree::Method<MTFn<()>, ()>
where
F: Fn(&Notification),
{
factory
.method("Notify", (), move |minfo| {
let mut i = minfo.msg.iter_init();
let appname: String = i.read()?;
let replaces_id: u32 = i.read()?;
let icon: String = i.read()?;
let summary: String = i.read()?;
let body: String = i.read()?;
let actions: Vec<String> = i.read()?;
let hints: ::std::collections::HashMap<String, arg::Variant<Box<dyn RefArg>>> =
i.read()?;
let timeout: i32 = i.read()?;
println!("hints {:?} ", hints);
// let arg0 = try!(d.notify(app_name, replaces_id, app_icon, summary, body, actions, hints, timeout));
let notification = Notification {
appname,
icon,
summary,
body,
actions,
hints: hints_from_variants(&hints),
timeout: Timeout::from(timeout),
id: if replaces_id == 0 {
None
} else {
Some(replaces_id)
},
subtitle: None,
};
on_notification(&notification);
let arg0 = 43;
let rm = minfo.msg.method_return();
let rm = rm.append1(arg0);
Ok(vec![rm])
})
.in_arg(("app_name", "s"))
.in_arg(("replaces_id", "u"))
.in_arg(("app_icon", "s"))
.in_arg(("summary", "s"))
.in_arg(("body", "s"))
.in_arg(("actions", "as"))
.in_arg(("hints", "a{sv}"))
.in_arg(("timeout", "i"))
.out_arg(("", "u"))
}
fn method_close_notification(factory: &Factory<MTFn>) -> tree::Method<MTFn<()>, ()> {
factory
.method("CloseNotification", (), |minfo| {
let i = minfo.msg.iter_init();
let rm = minfo.msg.method_return();
Ok(vec![rm])
})
.in_arg(("id", "u"))
}
fn method_get_capabilities(factory: &Factory<MTFn>) -> tree::Method<MTFn<()>, ()> {
factory
.method("GetCapabilities", (), |minfo| {
let caps: Vec<String> = vec![];
let rm = minfo.msg.method_return();
let rm = rm.append1(caps);
Ok(vec![rm])
})
.out_arg(("caps", "as"))
}
fn method_get_server_information(factory: &Factory<MTFn>) -> tree::Method<MTFn<()>, ()> {
factory
.method("GetServerInformation", (), |minfo| {
let (name, vendor, version, spec_version) = (
"notify-rust",
"notify-rust",
env!("CARGO_PKG_VERSION"),
"0.0.0",
);
let rm = minfo.msg.method_return();
let rm = rm.append1(name);
let rm = rm.append1(vendor);
let rm = rm.append1(version);
let rm = rm.append1(spec_version);
Ok(vec![rm])
})
.out_arg(("name", "s"))
.out_arg(("vendor", "s"))
.out_arg(("version", "s"))
.out_arg(("spec_version", "s"))
}

@ -1,102 +0,0 @@
use std::{convert::TryInto, num::ParseIntError, str::FromStr, time::Duration};
/// Describes the timeout of a notification
///
/// # `FromStr`
/// You can also parse a `Timeout` from a `&str`.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Timeout {
/// Expires according to server default.
///
/// Whatever that might be...
Default,
/// Do not expire, user will have to close this manually.
Never,
/// Expire after n milliseconds.
Milliseconds(u32),
}
impl Default for Timeout {
fn default() -> Self {
Timeout::Default
}
}
#[test]
fn timeout_from_i32() {
assert_eq!(Timeout::from(234), Timeout::Milliseconds(234));
assert_eq!(Timeout::from(-234), Timeout::Default);
assert_eq!(Timeout::from(0), Timeout::Never);
}
impl From<i32> for Timeout {
fn from(int: i32) -> Timeout {
use std::cmp::Ordering::*;
match int.cmp(&0) {
Greater => Timeout::Milliseconds(int as u32),
Less => Timeout::Default,
Equal => Timeout::Never,
}
}
}
impl From<Duration> for Timeout {
fn from(duration: Duration) -> Timeout {
if duration.is_zero() {
Timeout::Never
} else if duration.as_millis() > u32::MAX.into() {
Timeout::Default
} else {
Timeout::Milliseconds(duration.as_millis().try_into().unwrap_or(u32::MAX))
}
}
}
impl From<Timeout> for i32 {
fn from(timeout: Timeout) -> Self {
match timeout {
Timeout::Default => -1,
Timeout::Never => 0,
Timeout::Milliseconds(ms) => ms as i32,
}
}
}
impl FromStr for Timeout {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"default" => Ok(Timeout::Default),
"never" => Ok(Timeout::Never),
milliseconds => Ok(Timeout::Milliseconds(u32::from_str(milliseconds)?)),
}
}
}
pub struct TimeoutMessage(Timeout);
impl From<Timeout> for TimeoutMessage {
fn from(hint: Timeout) -> Self {
TimeoutMessage(hint)
}
}
impl std::ops::Deref for TimeoutMessage {
type Target = Timeout;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
impl TryFrom<&dbus::arg::messageitem::MessageItem> for TimeoutMessage {
type Error = ();
fn try_from(mi: &dbus::arg::messageitem::MessageItem) -> Result<TimeoutMessage, ()> {
mi.inner::<i32>().map(|i| TimeoutMessage(i.into()))
}
}

@ -1,74 +0,0 @@
use super::error::ErrorKind;
use std::convert::TryFrom;
/// Levels of Urgency.
///
/// # Specification
/// > Developers must use their own judgement when deciding the urgency of a notification. Typically, if the majority of programs are using the same level for a specific type of urgency, other applications should follow them.
/// >
/// > For low and normal urgencies, server implementations may display the notifications how they choose. They should, however, have a sane expiration timeout dependent on the urgency level.
/// >
/// > **Critical notifications should not automatically expire**, as they are things that the user will most likely want to know about. They should only be closed when the user dismisses them, for example, by clicking on the notification.
///
/// <cite> — see [Galago](http://www.galago-project.org/specs/notification/0.9/x320.html) or [Gnome](https://developer.gnome.org/notification-spec/#urgency-levels) specification.</cite>
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
pub enum Urgency {
/// The behavior for `Low` urgency depends on the notification server.
Low = 0,
/// The behavior for `Normal` urgency depends on the notification server.
Normal = 1,
/// A critical notification will not time out.
Critical = 2,
}
impl TryFrom<&str> for Urgency {
type Error = super::error::Error;
#[rustfmt::skip]
fn try_from(string: &str) -> Result<Urgency, Self::Error> {
match string.to_lowercase().as_ref() {
"low" |
"lo" => Ok(Urgency::Low),
"normal" |
"medium" => Ok(Urgency::Normal),
"critical" |
"high" |
"hi" => Ok(Urgency::Critical),
_ => Err(ErrorKind::Conversion(format!("invalid input {:?}", string)).into())
}
}
}
impl From<Option<u64>> for Urgency {
fn from(maybe_int: Option<u64>) -> Urgency {
match maybe_int {
Some(0) => Urgency::Low,
Some(x) if x >= 2 => Urgency::Critical,
_ => Urgency::Normal,
}
}
}
// TODO: remove this in v5.0
#[cfg(not(feature = "server"))]
impl From<u64> for Urgency {
fn from(int: u64) -> Urgency {
match int {
0 => Urgency::Low,
1 => Urgency::Normal,
2..=std::u64::MAX => Urgency::Critical,
}
}
}
// TODO: make this the default in v5.0
#[cfg(feature = "server")]
impl From<u8> for Urgency {
fn from(int: u8) -> Urgency {
match int {
0 => Urgency::Low,
1 => Urgency::Normal,
2..=std::u8::MAX => Urgency::Critical,
}
}
}

@ -1,40 +0,0 @@
use winrt_notification::Toast;
pub use super::{error::*, notification::Notification, timeout::Timeout};
use std::{path::Path, str::FromStr};
pub(crate) fn show_notification(notification: &Notification) -> Result<()> {
let sound = match &notification.sound_name {
Some(chosen_sound_name) => winrt_notification::Sound::from_str(chosen_sound_name).ok(),
None => None,
};
let duration = match notification.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 = &notification.app_id.as_ref().unwrap_or(powershell_app_id);
let mut toast = Toast::new(app_id)
.title(&notification.summary)
.text1(notification.subtitle.as_ref().map_or("", AsRef::as_ref)) // subtitle
.text2(&notification.body)
.sound(sound)
.duration(duration);
if let Some(image_path) = &notification.path_to_image {
toast = toast.image(Path::new(&image_path), "");
}
toast
.show()
.map_err(|e| Error::from(ErrorKind::Msg(format!("{:?}", e))))
}

@ -1,68 +0,0 @@
use super::super::xdg::NOTIFICATION_DEFAULT_BUS;
fn skip_first_slash(s: &str) -> &str {
if let Some('/') = s.chars().next() {
&s[1..]
} else {
s
}
}
use std::path::PathBuf;
type BusNameType = std::borrow::Cow<'static, str>;
#[derive(Clone, Debug)]
pub struct NotificationBus(BusNameType);
impl Default for NotificationBus {
#[cfg(feature = "zbus")]
fn default() -> Self {
Self(
zbus::names::WellKnownName::from_static_str(NOTIFICATION_DEFAULT_BUS)
.unwrap()
.to_string()
.into(),
)
}
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
fn default() -> Self {
Self(
dbus::strings::BusName::from_slice(NOTIFICATION_DEFAULT_BUS)
.unwrap()
.to_string()
.into(),
)
}
}
impl NotificationBus {
fn namespaced_custom(custom_path: &str) -> Option<String> {
// abusing path for semantic join
skip_first_slash(
PathBuf::from("/de/hoodie/Notification")
.join(custom_path)
.to_str()?,
)
.replace('/', ".")
.into()
}
#[cfg(feature = "zbus")]
pub fn custom(custom_path: &str) -> Option<Self> {
let name =
zbus::names::WellKnownName::try_from(Self::namespaced_custom(custom_path)?).ok()?;
Some(Self(name.to_string().into()))
}
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
pub fn custom(custom_path: &str) -> Option<Self> {
let name = dbus::strings::BusName::new(Self::namespaced_custom(custom_path)?).ok()?;
Some(Self(name.to_string().into()))
}
pub fn into_name(self) -> BusNameType {
self.0
}
}

@ -1,328 +0,0 @@
use dbus::{
arg::messageitem::{MessageItem, MessageItemArray},
ffidisp::{BusType, Connection, ConnectionItem},
Message,
};
use super::{
bus::NotificationBus, ActionResponse, ActionResponseHandler, CloseReason,
NOTIFICATION_INTERFACE,
};
use super::super::{
error::*,
hints::message::HintMessage,
notification::Notification,
xdg::{ServerInformation, NOTIFICATION_OBJECTPATH},
};
pub mod bus {
use super::super::super::xdg::NOTIFICATION_DEFAULT_BUS;
fn skip_first_slash(s: &str) -> &str {
if let Some('/') = s.chars().next() {
&s[1..]
} else {
s
}
}
use std::path::PathBuf;
type BusNameType = dbus::strings::BusName<'static>;
#[derive(Clone, Debug)]
pub struct NotificationBus(BusNameType);
impl Default for NotificationBus {
fn default() -> Self {
Self(dbus::strings::BusName::from_slice(NOTIFICATION_DEFAULT_BUS).unwrap())
}
}
impl NotificationBus {
fn namespaced_custom(custom_path: &str) -> Option<String> {
// abusing path for semantic join
skip_first_slash(
PathBuf::from("/de/hoodie/Notification")
.join(custom_path)
.to_str()?,
)
.replace('/', ".")
.into()
}
pub fn custom(custom_path: &str) -> Option<Self> {
let name = dbus::strings::BusName::new(Self::namespaced_custom(custom_path)?).ok()?;
Some(Self(name))
}
pub fn into_name(self) -> BusNameType {
self.0
}
}
}
/// A handle to a shown notification.
///
/// This keeps a connection alive to ensure actions work on certain desktops.
#[derive(Debug)]
pub struct DbusNotificationHandle {
pub(crate) id: u32,
pub(crate) connection: Connection,
pub(crate) notification: Notification,
}
impl DbusNotificationHandle {
pub(crate) fn new(
id: u32,
connection: Connection,
notification: Notification,
) -> DbusNotificationHandle {
DbusNotificationHandle {
id,
connection,
notification,
}
}
pub fn wait_for_action(self, invocation_closure: impl ActionResponseHandler) {
wait_for_action_signal(&self.connection, self.id, invocation_closure);
}
pub fn close(self) {
let mut message = build_message("CloseNotification", Default::default());
message.append_items(&[self.id.into()]);
let _ = self.connection.send(message); // If closing fails there's nothing we could do anyway
}
pub fn on_close<F>(self, closure: F)
where
F: FnOnce(CloseReason),
{
self.wait_for_action(|action: &ActionResponse| {
if let ActionResponse::Closed(reason) = action {
closure(*reason);
}
});
}
pub fn update(&mut self) {
self.id = send_notification_via_connection(&self.notification, self.id, &self.connection)
.unwrap();
}
}
pub fn send_notification_via_connection(
notification: &Notification,
id: u32,
connection: &Connection,
) -> Result<u32> {
send_notification_via_connection_at_bus(notification, id, connection, Default::default())
}
pub fn send_notification_via_connection_at_bus(
notification: &Notification,
id: u32,
connection: &Connection,
bus: NotificationBus,
) -> Result<u32> {
let mut message = build_message("Notify", bus);
let timeout: i32 = notification.timeout.into();
message.append_items(&[
notification.appname.to_owned().into(), // appname
id.into(), // notification to update
notification.icon.to_owned().into(), // icon
notification.summary.to_owned().into(), // summary (title)
notification.body.to_owned().into(), // body
pack_actions(notification), // actions
pack_hints(notification)?, // hints
timeout.into(), // timeout
]);
let reply = connection.send_with_reply_and_block(message, 2000)?;
match reply.get_items().first() {
Some(MessageItem::UInt32(ref id)) => Ok(*id),
_ => Ok(0),
}
}
pub fn connect_and_send_notification(
notification: &Notification,
) -> Result<DbusNotificationHandle> {
let bus = notification.bus.clone();
connect_and_send_notification_at_bus(notification, bus)
}
pub fn connect_and_send_notification_at_bus(
notification: &Notification,
bus: NotificationBus,
) -> Result<DbusNotificationHandle> {
let connection = Connection::get_private(BusType::Session)?;
let inner_id = notification.id.unwrap_or(0);
let id = send_notification_via_connection_at_bus(notification, inner_id, &connection, bus)?;
Ok(DbusNotificationHandle::new(
id,
connection,
notification.clone(),
))
}
fn build_message(method_name: &str, bus: NotificationBus) -> Message {
Message::new_method_call(
bus.into_name(),
NOTIFICATION_OBJECTPATH,
NOTIFICATION_INTERFACE,
method_name,
)
.unwrap_or_else(|_| panic!("Error building message call {:?}.", method_name))
}
pub fn pack_hints(notification: &Notification) -> Result<MessageItem> {
if !notification.hints.is_empty() || !notification.hints_unique.is_empty() {
let hints = notification
.get_hints()
.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(),
))
}
pub fn pack_actions(notification: &Notification) -> MessageItem {
if !notification.actions.is_empty() {
let mut actions = vec![];
for action in &notification.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())
}
pub fn get_capabilities() -> Result<Vec<String>> {
let mut capabilities = vec![];
let message = build_message("GetCapabilities", Default::default());
let connection = Connection::get_private(BusType::Session)?;
let reply = connection.send_with_reply_and_block(message, 2000)?;
if let Some(MessageItem::Array(items)) = reply.get_items().first() {
for item in items.iter() {
if let MessageItem::Str(ref cap) = *item {
capabilities.push(cap.clone());
}
}
}
Ok(capabilities)
}
fn unwrap_message_string(item: Option<&MessageItem>) -> String {
match item {
Some(MessageItem::Str(value)) => value.to_owned(),
_ => "".to_owned(),
}
}
#[allow(clippy::get_first)]
pub fn get_server_information() -> Result<ServerInformation> {
let message = build_message("GetServerInformation", Default::default());
let connection = Connection::get_private(BusType::Session)?;
let reply = connection.send_with_reply_and_block(message, 2000)?;
let items = reply.get_items();
Ok(ServerInformation {
name: unwrap_message_string(items.get(0)),
vendor: unwrap_message_string(items.get(1)),
version: unwrap_message_string(items.get(2)),
spec_version: unwrap_message_string(items.get(3)),
})
}
/// Listens for the `ActionInvoked(UInt32, String)` Signal.
///
/// No need to use this, check out `Notification::show_and_wait_for_action(FnOnce(action:&str))`
pub fn handle_action(id: u32, func: impl ActionResponseHandler) {
let connection = Connection::get_private(BusType::Session).unwrap();
wait_for_action_signal(&connection, id, func);
}
// Listens for the `ActionInvoked(UInt32, String)` signal.
fn wait_for_action_signal(connection: &Connection, id: u32, handler: impl ActionResponseHandler) {
connection
.add_match(&format!(
"interface='{}',member='ActionInvoked'",
NOTIFICATION_INTERFACE
))
.unwrap();
connection
.add_match(&format!(
"interface='{}',member='NotificationClosed'",
NOTIFICATION_INTERFACE
))
.unwrap();
for item in connection.iter(1000) {
if let ConnectionItem::Signal(message) = item {
let items = message.get_items();
let (path, interface, member) = (
message.path().map_or_else(String::new, |p| {
p.into_cstring().to_string_lossy().into_owned()
}),
message.interface().map_or_else(String::new, |p| {
p.into_cstring().to_string_lossy().into_owned()
}),
message.member().map_or_else(String::new, |p| {
p.into_cstring().to_string_lossy().into_owned()
}),
);
match (path.as_str(), interface.as_str(), member.as_str()) {
// match (protocol.unwrap(), iface.unwrap(), member.unwrap()) {
// Action Invoked
(path, interface, "ActionInvoked")
if path == NOTIFICATION_OBJECTPATH && interface == NOTIFICATION_INTERFACE =>
{
if let (&MessageItem::UInt32(nid), MessageItem::Str(ref action)) =
(&items[0], &items[1])
{
if nid == id {
handler.call(&ActionResponse::Custom(action));
break;
}
}
}
// Notification Closed
(path, interface, "NotificationClosed")
if path == NOTIFICATION_OBJECTPATH && interface == NOTIFICATION_INTERFACE =>
{
if let (&MessageItem::UInt32(nid), &MessageItem::UInt32(reason)) =
(&items[0], &items[1])
{
if nid == id {
handler.call(&ActionResponse::Closed(reason.into()));
break;
}
}
}
(..) => (),
}
}
}
}

@ -1,544 +0,0 @@
//! This module contains `XDG` and `DBus` specific code.
//!
//! it should not be available under any platform other than `(unix, not(target_os = "macos"))`
#[cfg(feature = "dbus")]
use dbus::ffidisp::Connection as DbusConnection;
#[cfg(feature = "zbus")]
use zbus::{block_on, zvariant};
use super::{error::*, notification::Notification};
use std::ops::{Deref, DerefMut};
#[cfg(feature = "dbus")]
mod dbus_rs;
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
use dbus_rs::bus;
#[cfg(feature = "zbus")]
mod zbus_rs;
#[cfg(all(feature = "zbus", not(feature = "dbus")))]
use zbus_rs::bus;
#[cfg(all(feature = "dbus", feature = "zbus"))]
mod bus;
// #[cfg(all(feature = "server", feature = "dbus", unix, not(target_os = "macos")))]
// pub mod server_dbus;
// #[cfg(all(feature = "server", feature = "zbus", unix, not(target_os = "macos")))]
// pub mod server_zbus;
// #[cfg(all(feature = "server", unix, not(target_os = "macos")))]
// pub mod server;
#[cfg(not(feature = "debug_namespace"))]
#[doc(hidden)]
pub static NOTIFICATION_DEFAULT_BUS: &str = "org.freedesktop.Notifications";
#[cfg(feature = "debug_namespace")]
#[doc(hidden)]
// #[deprecated]
pub static NOTIFICATION_DEFAULT_BUS: &str = "de.hoodie.Notifications";
#[doc(hidden)]
pub static NOTIFICATION_INTERFACE: &str = "org.freedesktop.Notifications";
#[doc(hidden)]
pub static NOTIFICATION_OBJECTPATH: &str = "/org/freedesktop/Notifications";
pub(crate) use bus::NotificationBus;
#[derive(Debug)]
enum NotificationHandleInner {
#[cfg(feature = "dbus")]
Dbus(dbus_rs::DbusNotificationHandle),
#[cfg(feature = "zbus")]
Zbus(zbus_rs::ZbusNotificationHandle),
}
/// A handle to a shown notification.
///
/// This keeps a connection alive to ensure actions work on certain desktops.
#[derive(Debug)]
pub struct NotificationHandle {
inner: NotificationHandleInner,
}
#[allow(dead_code)]
impl NotificationHandle {
#[cfg(feature = "dbus")]
pub(crate) fn for_dbus(
id: u32,
connection: DbusConnection,
notification: Notification,
) -> NotificationHandle {
NotificationHandle {
inner: dbus_rs::DbusNotificationHandle::new(id, connection, notification).into(),
}
}
#[cfg(feature = "zbus")]
pub(crate) fn for_zbus(
id: u32,
connection: zbus::Connection,
notification: Notification,
) -> NotificationHandle {
NotificationHandle {
inner: zbus_rs::ZbusNotificationHandle::new(id, connection, notification).into(),
}
}
/// Waits for the user to act on a notification and then calls
/// `invocation_closure` with the name of the corresponding action.
pub fn wait_for_action<F>(self, invocation_closure: F)
where
F: FnOnce(&str),
{
match self.inner {
#[cfg(feature = "dbus")]
NotificationHandleInner::Dbus(inner) => {
inner.wait_for_action(|action: &ActionResponse| match action {
ActionResponse::Custom(action) => invocation_closure(action),
ActionResponse::Closed(_reason) => invocation_closure("__closed"), // FIXME: remove backward compatibility with 5.0
});
}
#[cfg(feature = "zbus")]
NotificationHandleInner::Zbus(inner) => {
block_on(
inner.wait_for_action(|action: &ActionResponse| match action {
ActionResponse::Custom(action) => invocation_closure(action),
ActionResponse::Closed(_reason) => invocation_closure("__closed"), // FIXME: remove backward compatibility with 5.0
}),
);
}
};
}
/// Manually close the notification
pub fn close(self) {
match self.inner {
#[cfg(feature = "dbus")]
NotificationHandleInner::Dbus(inner) => inner.close(),
#[cfg(feature = "zbus")]
NotificationHandleInner::Zbus(inner) => block_on(inner.close()),
}
}
/// Executes a closure after the notification has closed.
pub fn on_close<A>(self, handler: impl CloseHandler<A>) {
match self.inner {
#[cfg(feature = "dbus")]
NotificationHandleInner::Dbus(inner) => {
inner.wait_for_action(|action: &ActionResponse| {
if let ActionResponse::Closed(reason) = action {
handler.call(*reason);
}
});
}
#[cfg(feature = "zbus")]
NotificationHandleInner::Zbus(inner) => {
block_on(inner.wait_for_action(|action: &ActionResponse| {
if let ActionResponse::Closed(reason) = action {
handler.call(*reason);
}
}));
}
};
}
pub fn update(&mut self) {
match self.inner {
#[cfg(feature = "dbus")]
NotificationHandleInner::Dbus(ref mut inner) => inner.update(),
#[cfg(feature = "zbus")]
NotificationHandleInner::Zbus(ref mut inner) => inner.update(),
}
}
/// Returns the Handle's id.
pub fn id(&self) -> u32 {
match self.inner {
#[cfg(feature = "dbus")]
NotificationHandleInner::Dbus(ref inner) => inner.id,
#[cfg(feature = "zbus")]
NotificationHandleInner::Zbus(ref inner) => inner.id,
}
}
}
/// Required for `DerefMut`
impl Deref for NotificationHandle {
type Target = Notification;
fn deref(&self) -> &Notification {
match self.inner {
#[cfg(feature = "dbus")]
NotificationHandleInner::Dbus(ref inner) => &inner.notification,
#[cfg(feature = "zbus")]
NotificationHandleInner::Zbus(ref inner) => &inner.notification,
}
}
}
/// Allow you to easily modify notification properties
impl DerefMut for NotificationHandle {
fn deref_mut(&mut self) -> &mut Notification {
match self.inner {
#[cfg(feature = "dbus")]
NotificationHandleInner::Dbus(ref mut inner) => &mut inner.notification,
#[cfg(feature = "zbus")]
NotificationHandleInner::Zbus(ref mut inner) => &mut inner.notification,
}
}
}
#[cfg(feature = "dbus")]
impl From<dbus_rs::DbusNotificationHandle> for NotificationHandleInner {
fn from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandleInner {
NotificationHandleInner::Dbus(handle)
}
}
#[cfg(feature = "zbus")]
impl From<zbus_rs::ZbusNotificationHandle> for NotificationHandleInner {
fn from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandleInner {
NotificationHandleInner::Zbus(handle)
}
}
#[cfg(feature = "dbus")]
impl From<dbus_rs::DbusNotificationHandle> for NotificationHandle {
fn from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandle {
NotificationHandle {
inner: handle.into(),
}
}
}
#[cfg(feature = "zbus")]
impl From<zbus_rs::ZbusNotificationHandle> for NotificationHandle {
fn from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandle {
NotificationHandle {
inner: handle.into(),
}
}
}
// here be public functions
// TODO: breaking change, wait for 5.0
// #[cfg(all(feature = "dbus", feature = "zbus"))]
//compile_error!("the z and d features are mutually exclusive");
#[cfg(all(
not(any(feature = "dbus", feature = "zbus")),
unix,
not(target_os = "macos")
))]
compile_error!("you have to build with either zbus or dbus turned on");
/// Which Dbus implementation are we using?
#[derive(Copy, Clone, Debug)]
pub enum DbusStack {
/// using [dbus-rs](https://docs.rs/dbus-rs)
Dbus,
/// using [zbus](https://docs.rs/zbus)
Zbus,
}
#[cfg(all(feature = "dbus", feature = "zbus"))]
const DBUS_SWITCH_VAR: &str = "DBUSRS";
#[cfg(all(feature = "zbus", not(feature = "dbus")))]
pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
block_on(zbus_rs::connect_and_send_notification(notification)).map(Into::into)
}
#[cfg(all(feature = "async", feature = "zbus"))]
pub(crate) async fn show_notification_async(
notification: &Notification,
) -> Result<NotificationHandle> {
zbus_rs::connect_and_send_notification(notification)
.await
.map(Into::into)
}
#[cfg(all(feature = "async", feature = "zbus"))]
pub(crate) async fn show_notification_async_at_bus(
notification: &Notification,
bus: NotificationBus,
) -> Result<NotificationHandle> {
zbus_rs::connect_and_send_notification_at_bus(notification, bus)
.await
.map(Into::into)
}
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
dbus_rs::connect_and_send_notification(notification).map(Into::into)
}
#[cfg(all(feature = "dbus", feature = "zbus"))]
pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
if std::env::var(DBUS_SWITCH_VAR).is_ok() {
dbus_rs::connect_and_send_notification(notification).map(Into::into)
} else {
block_on(zbus_rs::connect_and_send_notification(notification)).map(Into::into)
}
}
/// Get the currently used [`DbusStack`]
///
/// (zbus only)
#[cfg(all(feature = "zbus", not(feature = "dbus")))]
pub fn dbus_stack() -> Option<DbusStack> {
Some(DbusStack::Zbus)
}
/// Get the currently used [`DbusStack`]
///
/// (dbus-rs only)
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
pub fn dbus_stack() -> Option<DbusStack> {
Some(DbusStack::Dbus)
}
/// Get the currently used [`DbusStack`]
///
/// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
#[cfg(all(feature = "dbus", feature = "zbus"))]
pub fn dbus_stack() -> Option<DbusStack> {
Some(if std::env::var(DBUS_SWITCH_VAR).is_ok() {
DbusStack::Dbus
} else {
DbusStack::Zbus
})
}
/// Get the currently used [`DbusStack`]
///
/// neither zbus nor dbus-rs are configured
#[cfg(all(not(feature = "dbus"), not(feature = "zbus")))]
pub fn dbus_stack() -> Option<DbusStack> {
None
}
/// Get list of all capabilities of the running notification server.
///
/// (zbus only)
#[cfg(all(feature = "zbus", not(feature = "dbus")))]
pub fn get_capabilities() -> Result<Vec<String>> {
block_on(zbus_rs::get_capabilities())
}
/// Get list of all capabilities of the running notification server.
///
/// (dbus-rs only)
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
pub fn get_capabilities() -> Result<Vec<String>> {
dbus_rs::get_capabilities()
}
/// Get list of all capabilities of the running notification server.
///
/// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
#[cfg(all(feature = "dbus", feature = "zbus"))]
pub fn get_capabilities() -> Result<Vec<String>> {
if std::env::var(DBUS_SWITCH_VAR).is_ok() {
dbus_rs::get_capabilities()
} else {
block_on(zbus_rs::get_capabilities())
}
}
/// Returns a struct containing `ServerInformation`.
///
/// This struct contains `name`, `vendor`, `version` and `spec_version` of the notification server
/// running.
///
/// (zbus only)
#[cfg(all(feature = "zbus", not(feature = "dbus")))]
pub fn get_server_information() -> Result<ServerInformation> {
block_on(zbus_rs::get_server_information())
}
/// Returns a struct containing `ServerInformation`.
///
/// This struct contains `name`, `vendor`, `version` and `spec_version` of the notification server
/// running.
///
/// (dbus-rs only)
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
pub fn get_server_information() -> Result<ServerInformation> {
dbus_rs::get_server_information()
}
/// Returns a struct containing `ServerInformation`.
///
/// This struct contains `name`, `vendor`, `version` and `spec_version` of the notification server
/// running.
///
/// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
#[cfg(all(feature = "dbus", feature = "zbus"))]
pub fn get_server_information() -> Result<ServerInformation> {
if std::env::var(DBUS_SWITCH_VAR).is_ok() {
dbus_rs::get_server_information()
} else {
block_on(zbus_rs::get_server_information())
}
}
/// Return value of `get_server_information()`.
#[derive(Debug, serde::Deserialize)]
#[cfg_attr(feature = "zbus", derive(zvariant::Type))]
pub struct ServerInformation {
/// The product name of the server.
pub name: String,
/// The vendor name.
pub vendor: String,
/// The server's version string.
pub version: String,
/// The specification version the server is compliant with.
pub spec_version: String,
}
/// Strictly internal.
/// The NotificationServer implemented here exposes a "Stop" function.
/// stops the notification server
#[cfg(all(feature = "server", unix, not(target_os = "macos")))]
#[doc(hidden)]
pub fn stop_server() {
#[cfg(feature = "dbus")]
dbus_rs::stop_server()
}
/// Listens for the `ActionInvoked(UInt32, String)` Signal.
///
/// No need to use this, check out [`NotificationHandle::wait_for_action`]
/// (xdg only)
#[cfg(all(feature = "zbus", not(feature = "dbus")))]
// #[deprecated(note="please use [`NotificationHandle::wait_for_action`]")]
pub fn handle_action<F>(id: u32, func: F)
where
F: FnOnce(&ActionResponse),
{
block_on(zbus_rs::handle_action(id, func));
}
/// Listens for the `ActionInvoked(UInt32, String)` Signal.
///
/// No need to use this, check out [`NotificationHandle::wait_for_action`]
/// (xdg only)
#[cfg(all(feature = "dbus", not(feature = "zbus")))]
// #[deprecated(note="please use `NotificationHandle::wait_for_action`")]
pub fn handle_action<F>(id: u32, func: F)
where
F: FnOnce(&ActionResponse),
{
dbus_rs::handle_action(id, func);
}
/// Listens for the `ActionInvoked(UInt32, String)` Signal.
///
/// No need to use this, check out [`NotificationHandle::wait_for_action`]
/// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
#[cfg(all(feature = "dbus", feature = "zbus"))]
// #[deprecated(note="please use `NotificationHandle::wait_for_action`")]
pub fn handle_action<F>(id: u32, func: F)
where
F: FnOnce(&ActionResponse),
{
if std::env::var(DBUS_SWITCH_VAR).is_ok() {
dbus_rs::handle_action(id, func);
} else {
block_on(zbus_rs::handle_action(id, func));
}
}
/// Reason passed to `NotificationClosed` Signal
///
/// ## Specification
/// As listed under [Table 8. `NotificationClosed` Parameters](https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html#idm46350804042704)
#[derive(Copy, Clone, Debug)]
pub enum CloseReason {
/// The notification expired
Expired,
/// The notification was dismissed by the user
Dismissed,
/// The notification was closed by a call to `CloseNotification`
CloseAction,
/// Undefined/Reserved reason
Other(u32),
}
impl From<u32> for CloseReason {
fn from(raw_reason: u32) -> Self {
match raw_reason {
1 => CloseReason::Expired,
2 => CloseReason::Dismissed,
3 => CloseReason::CloseAction,
other => CloseReason::Other(other),
}
}
}
/// Helper Trait implemented by `Fn()`
pub trait ActionResponseHandler {
fn call(self, response: &ActionResponse);
}
// impl<F: Send + Sync + 'static> ActionResponseHandler for F
impl<F> ActionResponseHandler for F
where
F: FnOnce(&ActionResponse),
{
fn call(self, res: &ActionResponse) {
(self)(res);
}
}
/// Response to an action
pub enum ActionResponse<'a> {
/// Custom Action configured by the Notification.
Custom(&'a str),
/// The Notification was closed.
Closed(CloseReason),
}
impl<'a> From<&'a str> for ActionResponse<'a> {
fn from(raw: &'a str) -> Self {
Self::Custom(raw)
}
}
/// Your handy callback for the `Close` signal of your Notification.
///
/// This is implemented by `Fn()` and `Fn(CloseReason)`, so there is probably no good reason for you to manually implement this trait.
/// Should you find one anyway, please notify me and I'll gladly remove this obviously redundant comment.
pub trait CloseHandler<T> {
/// This is called with the [`CloseReason`].
fn call(&self, reason: CloseReason);
}
impl<F> CloseHandler<CloseReason> for F
where
F: Fn(CloseReason),
{
fn call(&self, reason: CloseReason) {
self(reason);
}
}
impl<F> CloseHandler<()> for F
where
F: Fn(),
{
fn call(&self, _: CloseReason) {
self();
}
}

@ -1,285 +0,0 @@
use super::super::{error::*, notification::Notification, xdg};
use zbus::{export::futures_util::TryStreamExt, MatchRule};
use super::{bus::NotificationBus, ActionResponse, ActionResponseHandler, CloseReason};
pub mod bus {
use super::super::super::xdg::NOTIFICATION_DEFAULT_BUS;
fn skip_first_slash(s: &str) -> &str {
if let Some('/') = s.chars().next() {
&s[1..]
} else {
s
}
}
use std::path::PathBuf;
type BusNameType = zbus::names::WellKnownName<'static>;
#[derive(Clone, Debug)]
pub struct NotificationBus(BusNameType);
impl Default for NotificationBus {
#[cfg(feature = "zbus")]
fn default() -> Self {
Self(zbus::names::WellKnownName::from_static_str(NOTIFICATION_DEFAULT_BUS).unwrap())
}
}
impl NotificationBus {
fn namespaced_custom(custom_path: &str) -> Option<String> {
// abusing path for semantic join
skip_first_slash(
PathBuf::from("/de/hoodie/Notification")
.join(custom_path)
.to_str()?,
)
.replace('/', ".")
.into()
}
pub fn custom(custom_path: &str) -> Option<Self> {
let name =
zbus::names::WellKnownName::try_from(Self::namespaced_custom(custom_path)?).ok()?;
Some(Self(name))
}
pub fn into_name(self) -> BusNameType {
self.0
}
}
}
/// A handle to a shown notification.
///
/// This keeps a connection alive to ensure actions work on certain desktops.
#[derive(Debug)]
pub struct ZbusNotificationHandle {
pub(crate) id: u32,
pub(crate) connection: zbus::Connection,
pub(crate) notification: Notification,
}
impl ZbusNotificationHandle {
pub(crate) fn new(
id: u32,
connection: zbus::Connection,
notification: Notification,
) -> ZbusNotificationHandle {
ZbusNotificationHandle {
id,
connection,
notification,
}
}
pub async fn wait_for_action(self, invocation_closure: impl ActionResponseHandler) {
wait_for_action_signal(&self.connection, self.id, invocation_closure).await;
}
pub async fn close_fallible(self) -> Result<()> {
self.connection
.call_method(
Some(self.notification.bus.clone().into_name()),
xdg::NOTIFICATION_OBJECTPATH,
Some(xdg::NOTIFICATION_INTERFACE),
"CloseNotification",
&(self.id),
)
.await?;
Ok(())
}
pub async fn close(self) {
self.close_fallible().await.unwrap();
}
pub fn on_close<F>(self, closure: F)
where
F: FnOnce(CloseReason),
{
zbus::block_on(self.wait_for_action(|action: &ActionResponse| {
if let ActionResponse::Closed(reason) = action {
closure(*reason);
}
}));
}
pub fn update_fallible(&mut self) -> Result<()> {
self.id = zbus::block_on(send_notification_via_connection(
&self.notification,
self.id,
&self.connection,
))?;
Ok(())
}
pub fn update(&mut self) {
self.update_fallible().unwrap();
}
}
async fn send_notification_via_connection(
notification: &Notification,
id: u32,
connection: &zbus::Connection,
) -> Result<u32> {
send_notification_via_connection_at_bus(notification, id, connection, Default::default()).await
}
async fn send_notification_via_connection_at_bus(
notification: &Notification,
id: u32,
connection: &zbus::Connection,
bus: NotificationBus,
) -> Result<u32> {
let reply: u32 = connection
.call_method(
Some(bus.into_name()),
xdg::NOTIFICATION_OBJECTPATH,
Some(xdg::NOTIFICATION_INTERFACE),
"Notify",
&(
&notification.appname,
id,
&notification.icon,
&notification.summary,
&notification.body,
&notification.actions,
super::super::hints::hints_to_map(notification),
i32::from(notification.timeout),
),
)
.await?
.body()
.deserialize()?;
Ok(reply)
}
pub async fn connect_and_send_notification(
notification: &Notification,
) -> Result<ZbusNotificationHandle> {
let bus = notification.bus.clone();
connect_and_send_notification_at_bus(notification, bus).await
}
pub(crate) async fn connect_and_send_notification_at_bus(
notification: &Notification,
bus: NotificationBus,
) -> Result<ZbusNotificationHandle> {
let connection = zbus::Connection::session().await?;
let inner_id = notification.id.unwrap_or(0);
let id =
send_notification_via_connection_at_bus(notification, inner_id, &connection, bus).await?;
Ok(ZbusNotificationHandle::new(
id,
connection,
notification.clone(),
))
}
pub async fn get_capabilities_at_bus(bus: NotificationBus) -> Result<Vec<String>> {
let connection = zbus::Connection::session().await?;
let info: Vec<String> = connection
.call_method(
Some(bus.into_name()),
xdg::NOTIFICATION_OBJECTPATH,
Some(xdg::NOTIFICATION_INTERFACE),
"GetCapabilities",
&(),
)
.await?
.body()
.deserialize()?;
Ok(info)
}
pub async fn get_capabilities() -> Result<Vec<String>> {
get_capabilities_at_bus(Default::default()).await
}
pub async fn get_server_information_at_bus(bus: NotificationBus) -> Result<xdg::ServerInformation> {
let connection = zbus::Connection::session().await?;
let info: xdg::ServerInformation = connection
.call_method(
Some(bus.into_name()),
xdg::NOTIFICATION_OBJECTPATH,
Some(xdg::NOTIFICATION_INTERFACE),
"GetServerInformation",
&(),
)
.await?
.body()
.deserialize()?;
Ok(info)
}
pub async fn get_server_information() -> Result<xdg::ServerInformation> {
get_server_information_at_bus(Default::default()).await
}
/// Listens for the `ActionInvoked(UInt32, String)` Signal.
///
/// No need to use this, check out `Notification::show_and_wait_for_action(FnOnce(action:&str))`
pub async fn handle_action(id: u32, func: impl ActionResponseHandler) {
let connection = zbus::Connection::session().await.unwrap();
wait_for_action_signal(&connection, id, func).await;
}
async fn wait_for_action_signal(
connection: &zbus::Connection,
id: u32,
handler: impl ActionResponseHandler,
) {
let action_signal_rule = MatchRule::builder()
.msg_type(zbus::MessageType::Signal)
.interface(xdg::NOTIFICATION_INTERFACE)
.unwrap()
.member("ActionInvoked")
.unwrap()
.build();
let proxy = zbus::fdo::DBusProxy::new(connection).await.unwrap();
proxy.add_match_rule(action_signal_rule).await.unwrap();
let close_signal_rule = MatchRule::builder()
.msg_type(zbus::MessageType::Signal)
.interface(xdg::NOTIFICATION_INTERFACE)
.unwrap()
.member("NotificationClosed")
.unwrap()
.build();
proxy.add_match_rule(close_signal_rule).await.unwrap();
while let Ok(Some(msg)) = zbus::MessageStream::from(connection).try_next().await {
let header = msg.header();
if let zbus::MessageType::Signal = header.message_type() {
match header.member() {
Some(name) if name == "ActionInvoked" => {
match msg.body().deserialize::<(u32, String)>() {
Ok((nid, action)) if nid == id => {
handler.call(&ActionResponse::Custom(&action));
break;
}
_ => {}
}
}
Some(name) if name == "NotificationClosed" => {
match msg.body().deserialize::<(u32, u32)>() {
Ok((nid, reason)) if nid == id => {
handler.call(&ActionResponse::Closed(reason.into()));
break;
}
_ => {}
}
}
_ => {}
}
}
}
}
Loading…
Cancel
Save