notify_rust/
hints.rs

1#![cfg_attr(rustfmt, rustfmt_skip)]
2
3#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
4use zbus::zvariant;
5
6#[cfg(all(unix, not(target_os = "macos")))]
7pub(crate) mod message;
8
9#[cfg(all(feature = "images", any(feature = "dbus", feature = "zbus"), unix, not(target_os = "macos")))]
10use crate::image::Image;
11
12#[cfg(all(feature = "images", feature = "zbus", unix, not(target_os = "macos")))]
13use crate::image::image_spec_str;
14use crate::Urgency;
15
16#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))] use crate::notification::Notification;
17#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))] use std::collections::HashMap;
18
19mod constants;
20
21#[cfg(all(unix, not(target_os = "macos")))]
22#[derive(Eq, PartialEq, Hash, Clone, Debug)]
23pub(crate) enum CustomHintType {
24    Int,
25    String,
26}
27
28/// `Hints` allow you to pass extra information to the server.
29///
30/// Many of these are standardized by:
31///
32/// * <https://specifications.freedesktop.org/notification-spec/latest/hints.html>
33///
34/// Which of these are actually implemented depends strongly on the Notification server you talk to.
35/// Usually the [`get_capabilities()`](`crate::get_capabilities`) gives some clues, but the standards usually mention much more
36/// than is actually available.
37///
38/// you pass these to [`Notification::hint`]
39#[derive(Eq, PartialEq, Hash, Clone, Debug)]
40pub enum Hint {
41    /// If true, server may interpret action identifiers as named icons and display those.
42    ActionIcons(bool),
43
44    /// Check out:
45    ///
46    /// * <https://specifications.freedesktop.org/notification-spec/latest/hints.html>
47    Category(String),
48
49    /// Name of the `DesktopEntry` representing the calling application. In case of "firefox.desktop"
50    /// use "firefox". May be used to retrieve the correct icon.
51    DesktopEntry(String),
52
53    /// Image as raw data
54    #[cfg(all(feature = "images", unix, not(target_os = "macos")))]
55    ImageData(Image),
56
57    /// Display the image at this path.
58    ImagePath(String),
59
60    /// This does not work on all servers, however timeout=0 will do the job
61    Resident(bool),
62
63    /// Play the sound at this path.
64    SoundFile(String),
65
66    /// 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".
67    SoundName(String),
68
69    /// Suppress the notification sound.
70    SuppressSound(bool),
71
72    /// When set the server will treat the notification as transient and by-pass the server's persistence capability, if it should exist.
73    Transient(bool),
74
75    /// Lets the notification point to a certain 'x' position on the screen.
76    /// Requires `Y`.
77    X(i32),
78
79    /// Lets the notification point to a certain 'y' position on the screen.
80    /// Requires `X`.
81    Y(i32),
82
83    /// Pass me a Urgency, either Low, Normal or Critical
84    Urgency(Urgency),
85
86    /// If you want to pass something entirely different.
87    Custom(String, String),
88
89    /// A custom numerical (integer) hint
90    CustomInt(String, i32),
91
92    /// Only used by this `NotificationServer` implementation
93    Invalid // TODO find a better solution to this
94}
95
96impl Hint {
97    /// Get the `bool` representation of this hint.
98    pub fn as_bool(&self) -> Option<bool> {
99        match *self {
100            | Hint::ActionIcons(inner)
101            | Hint::Resident(inner)
102            | Hint::SuppressSound(inner)
103            | Hint::Transient(inner) => Some(inner),
104            _ => None
105        }
106    }
107
108    /// Get the `i32` representation of this hint.
109    pub fn as_i32(&self) -> Option<i32> {
110        match *self {
111            Hint::X(inner) | Hint::Y(inner) => Some(inner),
112            _ => None
113        }
114    }
115
116    /// Get the `&str` representation of this hint.
117    pub fn as_str(&self) -> Option<&str> {
118        match *self {
119            Hint::DesktopEntry(ref inner) |
120            Hint::ImagePath(ref inner)    |
121            Hint::SoundFile(ref inner)    |
122            Hint::SoundName(ref inner)    => Some(inner),
123            _ => None
124        }
125    }
126
127    /// convenience converting a name and value into a hint
128    pub fn from_key_val(name: &str, value: &str) -> Result<Hint, String> {
129        match (name,value){
130            (constants::ACTION_ICONS,val)    => val.parse::<bool>().map(Hint::ActionIcons).map_err(|e|e.to_string()),
131            (constants::CATEGORY, val)       => Ok(Hint::Category(val.to_owned())),
132            (constants::DESKTOP_ENTRY, val)  => Ok(Hint::DesktopEntry(val.to_owned())),
133            (constants::IMAGE_PATH, val)     => Ok(Hint::ImagePath(val.to_owned())),
134            (constants::RESIDENT, val)       => val.parse::<bool>().map(Hint::Resident).map_err(|e|e.to_string()),
135            (constants::SOUND_FILE, val)     => Ok(Hint::SoundFile(val.to_owned())),
136            (constants::SOUND_NAME, val)     => Ok(Hint::SoundName(val.to_owned())),
137            (constants::SUPPRESS_SOUND, val) => val.parse::<bool>().map(Hint::SuppressSound).map_err(|e|e.to_string()),
138            (constants::TRANSIENT, val)      => val.parse::<bool>().map(Hint::Transient).map_err(|e|e.to_string()),
139            (constants::X, val)              => val.parse::<i32>().map(Hint::X).map_err(|e|e.to_string()),
140            (constants::Y, val)              => val.parse::<i32>().map(Hint::Y).map_err(|e|e.to_string()),
141            _                                => Err(String::from("unknown name"))
142        }
143    }
144}
145
146#[cfg(all(unix, not(target_os = "macos")))]
147impl Hint {}
148
149#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
150#[test]
151fn test_hints_to_map() {
152
153    // custom value should only be there once if the names are identical
154
155    let n1 = Notification::new()
156        .hint(Hint::Custom("foo".into(), "bar1".into()))
157        .hint(Hint::Custom("foo".into(), "bar2".into()))
158        .hint(Hint::Custom("f00".into(), "bar3".into()))
159        .finalize();
160
161     assert_eq!(hints_to_map(&n1), maplit::hashmap!{
162         "foo" => zvariant::Value::Str("bar2".into()),
163         "f00" => zvariant::Value::Str("bar3".into())
164     });
165}
166
167#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
168pub(crate) fn hints_to_map(notification: &Notification) -> HashMap::<&str, zvariant::Value<'_>> {
169    notification
170        .get_hints()
171        .map(Into::into)
172        .collect()
173}
174
175#[cfg(all(feature = "zbus", unix, not(target_os = "macos")))]
176impl<'a> From<&'a Hint> for (&'a str, zvariant::Value<'a>) {
177    fn from(val: &'a Hint) -> Self {
178        use self::constants::*;
179        match val {
180            Hint::ActionIcons(value)       => (ACTION_ICONS   , zvariant::Value::Bool(*value)), // bool
181            Hint::Category(value)          => (CATEGORY       , zvariant::Value::Str(value.as_str().into())),
182            Hint::DesktopEntry(value)      => (DESKTOP_ENTRY  , zvariant::Value::Str(value.as_str().into())),
183
184            #[cfg(all(feature = "zbus", feature = "images", unix, not(target_os = "macos")))]
185            //Hint::ImageData(image)         => (image_spec(*crate::SPEC_VERSION).as_str(), ImagePayload::from(*image).into()),
186            Hint::ImageData(image)         => (
187                image_spec_str(*crate::SPEC_VERSION),
188                zvariant::Value::Structure(
189                    image.to_tuple().into()
190                )
191            ),
192
193
194            Hint::ImagePath(value)         => (IMAGE_PATH     , zvariant::Value::Str(value.as_str().into())),
195            Hint::Resident(value)          => (RESIDENT       , zvariant::Value::Bool(*value)), // bool
196            Hint::SoundFile(value)         => (SOUND_FILE     , zvariant::Value::Str(value.as_str().into())),
197            Hint::SoundName(value)         => (SOUND_NAME     , zvariant::Value::Str(value.as_str().into())),
198            Hint::SuppressSound(value)     => (SUPPRESS_SOUND , zvariant::Value::Bool(*value)),
199            Hint::Transient(value)         => (TRANSIENT      , zvariant::Value::Bool(*value)),
200            Hint::X(value)                 => (X              , zvariant::Value::I32(*value)),
201            Hint::Y(value)                 => (Y              , zvariant::Value::I32(*value)),
202            Hint::Urgency(value)           => (URGENCY        , zvariant::Value::U8(*value as u8)),
203            Hint::Custom(key, val)         => (key.as_str()   , zvariant::Value::Str(val.as_str().into())),
204            Hint::CustomInt(key, val)      => (key.as_str()   , zvariant::Value::I32(*val)),
205            Hint::Invalid                  => (INVALID        , zvariant::Value::Str(INVALID.into()))
206        }
207    }
208}
209
210
211#[cfg(all(feature = "dbus", unix, not(target_os = "macos")))]
212impl<'a, A: dbus::arg::RefArg> From<(&'a String, &'a A)> for Hint {
213    fn from(pair: (&String, &A)) -> Self {
214
215        let (key, variant) = pair;
216        match (key.as_ref(), variant.as_u64(), variant.as_i64(), variant.as_str().map(String::from)) {
217
218            (constants::ACTION_ICONS,   Some(1),  _,       _          ) => Hint::ActionIcons(true),
219            (constants::ACTION_ICONS,   _,        _,       _          ) => Hint::ActionIcons(false),
220            (constants::URGENCY,        level,    _,       _          ) => Hint::Urgency(level.into()),
221            (constants::CATEGORY,       _,        _,       Some(name) ) => Hint::Category(name),
222
223            (constants::DESKTOP_ENTRY,  _,        _,       Some(entry)) => Hint::DesktopEntry(entry),
224            (constants::IMAGE_PATH,     _,        _,       Some(path) ) => Hint::ImagePath(path),
225            (constants::RESIDENT,       Some(1),  _,       _          ) => Hint::Resident(true),
226            (constants::RESIDENT,       _,        _,       _          ) => Hint::Resident(false),
227
228            (constants::SOUND_FILE,     _,        _,       Some(path) ) => Hint::SoundFile(path),
229            (constants::SOUND_NAME,     _,        _,       Some(name) ) => Hint::SoundName(name),
230            (constants::SUPPRESS_SOUND, Some(1),  _,       _          ) => Hint::SuppressSound(true),
231            (constants::SUPPRESS_SOUND, _,        _,       _          ) => Hint::SuppressSound(false),
232            (constants::TRANSIENT,      Some(1),  _,       _          ) => Hint::Transient(true),
233            (constants::TRANSIENT,      _,        _,       _          ) => Hint::Transient(false),
234            (constants::X,              _,        Some(x), _          ) => Hint::X(x as i32),
235            (constants::Y,              _,        Some(y), _          ) => Hint::Y(y as i32),
236
237            other => {
238                eprintln!("Invalid Hint {:#?} ", other);
239                Hint::Invalid
240            }
241        }
242    }
243}