From 3779fb50634fba4d7e7eb0bfecc2216349b9d64d Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Tue, 11 Jun 2024 09:31:59 -0300 Subject: [PATCH] refactor(notification): pull notify_rust from crates.io (#1432) --- .changes/crate-notify-rust.md | 5 + Cargo.lock | 34 +- plugins/notification/Cargo.toml | 16 +- plugins/notification/src/desktop.rs | 4 +- plugins/notification/src/lib.rs | 3 - plugins/notification/src/notify_rust/error.rs | 161 ------ plugins/notification/src/notify_rust/hints.rs | 245 -------- .../src/notify_rust/hints/constants.rs | 16 - .../src/notify_rust/hints/message.rs | 159 ----- .../src/notify_rust/hints/tests.rs | 86 --- plugins/notification/src/notify_rust/image.rs | 229 -------- plugins/notification/src/notify_rust/macos.rs | 61 -- .../notification/src/notify_rust/miniver.rs | 75 --- plugins/notification/src/notify_rust/mod.rs | 173 ------ .../src/notify_rust/notification.rs | 480 ---------------- .../notification/src/notify_rust/server.rs | 238 -------- .../notification/src/notify_rust/timeout.rs | 102 ---- .../notification/src/notify_rust/urgency.rs | 74 --- .../notification/src/notify_rust/windows.rs | 40 -- .../notification/src/notify_rust/xdg/bus.rs | 68 --- .../src/notify_rust/xdg/dbus_rs.rs | 328 ----------- .../notification/src/notify_rust/xdg/mod.rs | 544 ------------------ .../src/notify_rust/xdg/zbus_rs.rs | 285 --------- 23 files changed, 25 insertions(+), 3401 deletions(-) create mode 100644 .changes/crate-notify-rust.md delete mode 100644 plugins/notification/src/notify_rust/error.rs delete mode 100644 plugins/notification/src/notify_rust/hints.rs delete mode 100644 plugins/notification/src/notify_rust/hints/constants.rs delete mode 100644 plugins/notification/src/notify_rust/hints/message.rs delete mode 100644 plugins/notification/src/notify_rust/hints/tests.rs delete mode 100644 plugins/notification/src/notify_rust/image.rs delete mode 100644 plugins/notification/src/notify_rust/macos.rs delete mode 100644 plugins/notification/src/notify_rust/miniver.rs delete mode 100644 plugins/notification/src/notify_rust/mod.rs delete mode 100644 plugins/notification/src/notify_rust/notification.rs delete mode 100644 plugins/notification/src/notify_rust/server.rs delete mode 100644 plugins/notification/src/notify_rust/timeout.rs delete mode 100644 plugins/notification/src/notify_rust/urgency.rs delete mode 100644 plugins/notification/src/notify_rust/windows.rs delete mode 100644 plugins/notification/src/notify_rust/xdg/bus.rs delete mode 100644 plugins/notification/src/notify_rust/xdg/dbus_rs.rs delete mode 100644 plugins/notification/src/notify_rust/xdg/mod.rs delete mode 100644 plugins/notification/src/notify_rust/xdg/zbus_rs.rs diff --git a/.changes/crate-notify-rust.md b/.changes/crate-notify-rust.md new file mode 100644 index 00000000..4a6a4766 --- /dev/null +++ b/.changes/crate-notify-rust.md @@ -0,0 +1,5 @@ +--- +"notification": patch +--- + +Use notify_rust from crates.io instead of local fork. diff --git a/Cargo.lock b/Cargo.lock index 053af3cf..14083a80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/plugins/notification/Cargo.toml b/plugins/notification/Cargo.toml index ffab5348..8cb8a77c 100644 --- a/plugins/notification/Cargo.toml +++ b/plugins/notification/Cargo.toml @@ -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" ] diff --git a/plugins/notification/src/desktop.rs b/plugins/notification/src/desktop.rs index f3f2a366..cef60e22 100644 --- a/plugins/notification/src/desktop.rs +++ b/plugins/notification/src/desktop.rs @@ -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 diff --git a/plugins/notification/src/lib.rs b/plugins/notification/src/lib.rs index 8c5086b0..e4a902cc 100644 --- a/plugins/notification/src/lib.rs +++ b/plugins/notification/src/lib.rs @@ -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)] diff --git a/plugins/notification/src/notify_rust/error.rs b/plugins/notification/src/notify_rust/error.rs deleted file mode 100644 index 923bf713..00000000 --- a/plugins/notification/src/notify_rust/error.rs +++ /dev/null @@ -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 = ::std::result::Result; - -#[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 for Error { - fn from(e: dbus::Error) -> Error { - Error { - kind: ErrorKind::Dbus(e), - } - } -} - -#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))] -impl From for Error { - fn from(e: zbus::Error) -> Error { - Error { - kind: ErrorKind::Zbus(e), - } - } -} - -#[cfg(target_os = "macos")] -impl From 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 for Error { - fn from(e: ImageError) -> Error { - Error { - kind: ErrorKind::Image(e), - } - } -} - -impl From for Error { - fn from(e: num::ParseIntError) -> Error { - Error { - kind: ErrorKind::Parse(e), - } - } -} - -impl From 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)*); - } - }; -} diff --git a/plugins/notification/src/notify_rust/hints.rs b/plugins/notification/src/notify_rust/hints.rs deleted file mode 100644 index 739e3502..00000000 --- a/plugins/notification/src/notify_rust/hints.rs +++ /dev/null @@ -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: -/// -/// * -/// * -/// -/// 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: - /// - /// * - /// * - 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 { - 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 { - 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 { - match (name,value){ - (constants::ACTION_ICONS,val) => val.parse::().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::().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::().map(Hint::SuppressSound).map_err(|e|e.to_string()), - (constants::TRANSIENT, val) => val.parse::().map(Hint::Transient).map_err(|e|e.to_string()), - (constants::X, val) => val.parse::().map(Hint::X).map_err(|e|e.to_string()), - (constants::Y, val) => val.parse::().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 - } - } - } -} diff --git a/plugins/notification/src/notify_rust/hints/constants.rs b/plugins/notification/src/notify_rust/hints/constants.rs deleted file mode 100644 index cbe6b86e..00000000 --- a/plugins/notification/src/notify_rust/hints/constants.rs +++ /dev/null @@ -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"; diff --git a/plugins/notification/src/notify_rust/hints/message.rs b/plugins/notification/src/notify_rust/hints/message.rs deleted file mode 100644 index 4e8d0e70..00000000 --- a/plugins/notification/src/notify_rust/hints/message.rs +++ /dev/null @@ -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 -#[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 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 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(hints: &HashMap) -> HashSet { - hints.iter().map(Into::into).collect() -} diff --git a/plugins/notification/src/notify_rust/hints/tests.rs b/plugins/notification/src/notify_rust/hints/tests.rs deleted file mode 100644 index 1fb3ddd8..00000000 --- a/plugins/notification/src/notify_rust/hints/tests.rs +++ /dev/null @@ -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")); -} diff --git a/plugins/notification/src/notify_rust/image.rs b/plugins/notification/src/notify_rust/image.rs deleted file mode 100644 index 27e33038..00000000 --- a/plugins/notification/src/notify_rust/image.rs +++ /dev/null @@ -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, -} - -impl Image { - fn from_raw_data( - width: i32, - height: i32, - data: Vec, - channels: i32, - bits_per_sample: i32, - alpha: bool, - ) -> Result { - 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) -> Result { - 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) -> Result { - 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 + Sized>(path: T) -> Result { - 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) { - ( - self.width, - self.height, - self.rowstride, - self.alpha, - self.bits_per_sample, - self.channels, - self.data.clone(), - ) - } -} - -impl TryFrom for Image { - type Error = ImageError; - - fn try_from(dyn_img: DynamicImage) -> Result { - match dyn_img { - DynamicImage::ImageRgb8(img) => Self::try_from(img), - DynamicImage::ImageRgba8(img) => Self::try_from(img), - _ => Err(ImageError::CantConvert), - } - } -} - -impl TryFrom for Image { - type Error = ImageError; - - fn try_from(img: image::RgbImage) -> Result { - let (width, height) = img.dimensions(); - let image_data = img.into_raw(); - Image::from_rgb(width as i32, height as i32, image_data) - } -} - -impl TryFrom for Image { - type Error = ImageError; - - fn try_from(img: image::RgbaImage) -> Result { - 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 for ImageMessage { - fn from(hint: Image) -> Self { - ImageMessage(hint) - } -} - -impl From 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 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()), - ]) - } -} diff --git a/plugins/notification/src/notify_rust/macos.rs b/plugins/notification/src/notify_rust/macos.rs deleted file mode 100644 index c886c0a7..00000000 --- a/plugins/notification/src/notify_rust/macos.rs +++ /dev/null @@ -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 { - mac_notification_sys::Notification::default() - .title(notification.summary.as_str()) - .message(¬ification.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 { - mac_notification_sys::Notification::default() - .title(notification.summary.as_str()) - .message(¬ification.body) - .maybe_subtitle(notification.subtitle.as_deref()) - .maybe_sound(notification.sound_name.as_deref()) - .delivery_date(delivery_date) - .send()?; - - Ok(NotificationHandle::new(notification.clone())) -} diff --git a/plugins/notification/src/notify_rust/miniver.rs b/plugins/notification/src/notify_rust/miniver.rs deleted file mode 100644 index 5c8deb83..00000000 --- a/plugins/notification/src/notify_rust/miniver.rs +++ /dev/null @@ -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 { - let vv = s.split('.').collect::>(); - 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 { - 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::().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)); - } -} diff --git a/plugins/notification/src/notify_rust/mod.rs b/plugins/notification/src/notify_rust/mod.rs deleted file mode 100644 index 6177db84..00000000 --- a/plugins/notification/src/notify_rust/mod.rs +++ /dev/null @@ -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 -//!
-//! ✔︎ = works
-//! ❌ = 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")))] -//! ``` -//!
-//! - -#![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::()) - .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, -} diff --git a/plugins/notification/src/notify_rust/notification.rs b/plugins/notification/src/notify_rust/notification.rs deleted file mode 100644 index 95e657b8..00000000 --- a/plugins/notification/src/notify_rust/notification.rs +++ /dev/null @@ -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, - - /// 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, - - #[cfg(all(unix, not(target_os = "macos")))] - pub(crate) hints_unique: HashMap<(String, CustomHintType), Hint>, - - /// See `Notification::actions()` and `Notification::action()` - pub actions: Vec, - - #[cfg(target_os = "macos")] - pub(crate) sound_name: Option, - - #[cfg(target_os = "windows")] - pub(crate) sound_name: Option, - - #[cfg(target_os = "windows")] - pub(crate) path_to_image: Option, - - #[cfg(target_os = "windows")] - pub(crate) app_id: Option, - - #[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, -} - -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 + 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 { - self.hints.iter().chain(self.hints_unique.values()) - } - - /// Set the `timeout`. - /// - /// Accepts multiple types that implement `Into`. - /// - /// ## `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>(&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 - self - } - - /// Set `actions`. - /// - /// To quote - /// - /// > 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) -> &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( - &self, - delivery_date: chrono::DateTime, - ) -> Result { - 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`. - #[cfg(target_os = "macos")] - pub fn schedule_raw(&self, timestamp: f64) -> Result { - 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::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::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 { - 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::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 { - 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, - } - } -} diff --git a/plugins/notification/src/notify_rust/server.rs b/plugins/notification/src/notify_rust/server.rs deleted file mode 100644 index c6802ece..00000000 --- a/plugins/notification/src/notify_rust/server.rs +++ /dev/null @@ -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>, - - /// A flag that stops the server - stopped: Mutex>, -} - -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 { - Arc::new(NotificationServer::default()) - } - // pub fn notify_mothod(&mut self, closure: F) - // -> Method - // where F: Fn(&Notification) - // { - - // fn handle_notification - - /// Start listening for incoming notifications - pub fn start(me: &Arc, 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>>, - factory: &Factory, - ) -> tree::Method, ()> { - 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(hints: &HashMap) -> HashSet { - hints.iter().map(Into::into).collect() -} - -fn method_notify( - factory: &Factory, - on_notification: F, -) -> tree::Method, ()> -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 = i.read()?; - let hints: ::std::collections::HashMap>> = - 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(¬ification); - - 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) -> tree::Method, ()> { - 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) -> tree::Method, ()> { - factory - .method("GetCapabilities", (), |minfo| { - let caps: Vec = 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) -> tree::Method, ()> { - 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")) -} diff --git a/plugins/notification/src/notify_rust/timeout.rs b/plugins/notification/src/notify_rust/timeout.rs deleted file mode 100644 index 6b76a55a..00000000 --- a/plugins/notification/src/notify_rust/timeout.rs +++ /dev/null @@ -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 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 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 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 { - match s { - "default" => Ok(Timeout::Default), - "never" => Ok(Timeout::Never), - milliseconds => Ok(Timeout::Milliseconds(u32::from_str(milliseconds)?)), - } - } -} - -pub struct TimeoutMessage(Timeout); - -impl From 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 { - mi.inner::().map(|i| TimeoutMessage(i.into())) - } -} diff --git a/plugins/notification/src/notify_rust/urgency.rs b/plugins/notification/src/notify_rust/urgency.rs deleted file mode 100644 index eeddf763..00000000 --- a/plugins/notification/src/notify_rust/urgency.rs +++ /dev/null @@ -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. -/// -/// — see [Galago](http://www.galago-project.org/specs/notification/0.9/x320.html) or [Gnome](https://developer.gnome.org/notification-spec/#urgency-levels) specification. -#[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 { - 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> for Urgency { - fn from(maybe_int: Option) -> 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 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 for Urgency { - fn from(int: u8) -> Urgency { - match int { - 0 => Urgency::Low, - 1 => Urgency::Normal, - 2..=std::u8::MAX => Urgency::Critical, - } - } -} diff --git a/plugins/notification/src/notify_rust/windows.rs b/plugins/notification/src/notify_rust/windows.rs deleted file mode 100644 index 423aae99..00000000 --- a/plugins/notification/src/notify_rust/windows.rs +++ /dev/null @@ -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 ¬ification.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 = ¬ification.app_id.as_ref().unwrap_or(powershell_app_id); - let mut toast = Toast::new(app_id) - .title(¬ification.summary) - .text1(notification.subtitle.as_ref().map_or("", AsRef::as_ref)) // subtitle - .text2(¬ification.body) - .sound(sound) - .duration(duration); - if let Some(image_path) = ¬ification.path_to_image { - toast = toast.image(Path::new(&image_path), ""); - } - - toast - .show() - .map_err(|e| Error::from(ErrorKind::Msg(format!("{:?}", e)))) -} diff --git a/plugins/notification/src/notify_rust/xdg/bus.rs b/plugins/notification/src/notify_rust/xdg/bus.rs deleted file mode 100644 index cd6c1cca..00000000 --- a/plugins/notification/src/notify_rust/xdg/bus.rs +++ /dev/null @@ -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 { - // 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 { - 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 { - 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 - } -} diff --git a/plugins/notification/src/notify_rust/xdg/dbus_rs.rs b/plugins/notification/src/notify_rust/xdg/dbus_rs.rs deleted file mode 100644 index b1658122..00000000 --- a/plugins/notification/src/notify_rust/xdg/dbus_rs.rs +++ /dev/null @@ -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 { - // 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 { - 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(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 { - 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 { - 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 { - 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 { - 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 { - if !notification.hints.is_empty() || !notification.hints_unique.is_empty() { - let hints = notification - .get_hints() - .cloned() - .map(HintMessage::wrap_hint) - .collect::>(); - - 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 ¬ification.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> { - 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 { - 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; - } - } - } - (..) => (), - } - } - } -} diff --git a/plugins/notification/src/notify_rust/xdg/mod.rs b/plugins/notification/src/notify_rust/xdg/mod.rs deleted file mode 100644 index 3012b570..00000000 --- a/plugins/notification/src/notify_rust/xdg/mod.rs +++ /dev/null @@ -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(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(self, handler: impl CloseHandler) { - 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 for NotificationHandleInner { - fn from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandleInner { - NotificationHandleInner::Dbus(handle) - } -} - -#[cfg(feature = "zbus")] -impl From for NotificationHandleInner { - fn from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandleInner { - NotificationHandleInner::Zbus(handle) - } -} - -#[cfg(feature = "dbus")] -impl From for NotificationHandle { - fn from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandle { - NotificationHandle { - inner: handle.into(), - } - } -} - -#[cfg(feature = "zbus")] -impl From 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 { - 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 { - 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 { - 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 { - dbus_rs::connect_and_send_notification(notification).map(Into::into) -} - -#[cfg(all(feature = "dbus", feature = "zbus"))] -pub(crate) fn show_notification(notification: &Notification) -> Result { - 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 { - Some(DbusStack::Zbus) -} - -/// Get the currently used [`DbusStack`] -/// -/// (dbus-rs only) -#[cfg(all(feature = "dbus", not(feature = "zbus")))] -pub fn dbus_stack() -> Option { - 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 { - 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 { - 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> { - 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> { - 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> { - 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 { - 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 { - 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 { - 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(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(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(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 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 ActionResponseHandler for F -impl 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 { - /// This is called with the [`CloseReason`]. - fn call(&self, reason: CloseReason); -} - -impl CloseHandler for F -where - F: Fn(CloseReason), -{ - fn call(&self, reason: CloseReason) { - self(reason); - } -} - -impl CloseHandler<()> for F -where - F: Fn(), -{ - fn call(&self, _: CloseReason) { - self(); - } -} diff --git a/plugins/notification/src/notify_rust/xdg/zbus_rs.rs b/plugins/notification/src/notify_rust/xdg/zbus_rs.rs deleted file mode 100644 index 7bf7017a..00000000 --- a/plugins/notification/src/notify_rust/xdg/zbus_rs.rs +++ /dev/null @@ -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 { - // 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 { - 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(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 { - 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 { - let reply: u32 = connection - .call_method( - Some(bus.into_name()), - xdg::NOTIFICATION_OBJECTPATH, - Some(xdg::NOTIFICATION_INTERFACE), - "Notify", - &( - ¬ification.appname, - id, - ¬ification.icon, - ¬ification.summary, - ¬ification.body, - ¬ification.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 { - 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 { - 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> { - let connection = zbus::Connection::session().await?; - let info: Vec = 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> { - get_capabilities_at_bus(Default::default()).await -} - -pub async fn get_server_information_at_bus(bus: NotificationBus) -> Result { - 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 { - 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; - } - _ => {} - } - } - _ => {} - } - } - } -}