#[cfg(all(unix, not(target_os = "macos")))]
use dbus::MessageItem;
use super::NotificationUrgency;
#[cfg(all(unix, not(target_os = "macos")))]
use util::*;
use miniver::Version;
use std::cmp::Ordering;
pub const ACTION_ICONS:&str = "action-icons";
pub const CATEGORY:&str = "category";
pub const DESKTOP_ENTRY:&str = "desktop-entry";
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";
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";
#[derive(PartialEq,Eq,Debug,Clone,Hash)]
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
pub struct NotificationImage {
width: i32,
height: i32,
rowstride: i32,
alpha: bool,
bits_per_sample: i32,
channels: i32,
data: Vec<u8>
}
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
impl NotificationImage {
pub fn from_rgb(width: i32, height: i32, data: Vec<u8>) -> Result<Self, ImageError> {
const MAX_SIZE:i32 = 0x0fff_ffff;
if width > MAX_SIZE || height > MAX_SIZE {
return Err(ImageError::TooBig)
}
let channels = 3i32;
let bits_per_sample = 8;
if data.len() != (width * height * channels) as usize {
Err(ImageError::WrongDataSize)
} else {
Ok(Self{
width: width,
height: height,
rowstride: width * channels,
alpha: false,
bits_per_sample: bits_per_sample,
channels: channels,
data: data,
})
}
}
}
#[derive(Clone,Copy,Debug,PartialEq,Eq)]
#[cfg(all(feature = "images",unix, not(target_os = "macos")))]
pub enum ImageError {
TooBig,
WrongDataSize
}
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
impl From<NotificationImage> for MessageItem {
fn from(img : NotificationImage) -> Self {
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(bytes, "y".into())
])
}
}
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
pub enum NotificationHint { ActionIcons(bool),
Category(String),
DesktopEntry(String),
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
ImageData(NotificationImage),
ImagePath(String),
Resident(bool),
SoundFile(String),
SoundName(String),
SuppressSound(bool),
Transient(bool),
X(i32),
Y(i32),
Urgency(NotificationUrgency),
Custom(String,String),
CustomInt(String, i32),
Invalid }
impl NotificationHint {
pub fn as_bool(&self) -> Option<bool> {
match *self {
NotificationHint::ActionIcons(inner) |
NotificationHint::Resident(inner) |
NotificationHint::SuppressSound(inner) |
NotificationHint::Transient(inner) => Some(inner),
_ => None
}
}
pub fn as_i32(&self) -> Option<i32> {
match *self {
NotificationHint::X(inner) |
NotificationHint::Y(inner) => Some(inner),
_ => None
}
}
pub fn as_str(&self) -> Option<&str> {
match *self {
NotificationHint::DesktopEntry(ref inner) |
NotificationHint::ImagePath(ref inner) |
NotificationHint::SoundFile(ref inner) |
NotificationHint::SoundName(ref inner) => Some(inner),
_ => None
}
}
}
pub fn hint_from_key_val(name: &str, value: &str) -> Result<NotificationHint, String>{
use NotificationHint as Hint;
match (name,value){
(ACTION_ICONS,val) => val.parse::<bool>().map(Hint::ActionIcons).map_err(|e|e.to_string()),
(CATEGORY, val) => Ok(Hint::Category(val.to_owned())),
(DESKTOP_ENTRY, val) => Ok(Hint::DesktopEntry(val.to_owned())),
(IMAGE_PATH, val) => Ok(Hint::ImagePath(val.to_owned())),
(RESIDENT, val) => val.parse::<bool>().map(Hint::Resident).map_err(|e|e.to_string()),
(SOUND_FILE, val) => Ok(Hint::SoundFile(val.to_owned())),
(SOUND_NAME, val) => Ok(Hint::SoundName(val.to_owned())),
(SUPPRESS_SOUND, val) => val.parse::<bool>().map(Hint::SuppressSound).map_err(|e|e.to_string()),
(TRANSIENT, val) => val.parse::<bool>().map(Hint::Transient).map_err(|e|e.to_string()),
(X, val) => val.parse::<i32>().map(Hint::X).map_err(|e|e.to_string()),
(Y, val) => val.parse::<i32>().map(Hint::Y).map_err(|e|e.to_string()),
_ => Err(String::from("unknown name"))
}
}
#[cfg(all(unix, not(target_os = "macos")))]
impl NotificationHint {
}
pub fn image_spec(version:Version) -> String {
match version.cmp(&Version::new(1,1)) {
Ordering::Less => IMAGE_DATA_1_0.to_owned(),
Ordering::Equal => IMAGE_DATA_1_1.to_owned(),
Ordering::Greater => IMAGE_DATA.to_owned()
}
}
#[cfg(all(unix, not(target_os = "macos")))]
impl<'a> From<&'a NotificationHint> for MessageItem {
fn from(hint: &'a NotificationHint) -> Self {
let hint:(String,MessageItem) = match *hint {
NotificationHint::ActionIcons(value) => (ACTION_ICONS .to_owned(), MessageItem::Bool(value)), NotificationHint::Category(ref value) => (CATEGORY .to_owned(), MessageItem::Str(value.clone())),
NotificationHint::DesktopEntry(ref value) => (DESKTOP_ENTRY .to_owned(), MessageItem::Str(value.clone())),
#[cfg(all(feature = "images", unix, not(target_os ="macos")))]
NotificationHint::ImageData(ref image) => (image_spec(*::SPEC_VERSION), image.clone().into()),
NotificationHint::ImagePath(ref value) => (IMAGE_PATH .to_owned(), MessageItem::Str(value.clone())),
NotificationHint::Resident(value) => (RESIDENT .to_owned(), MessageItem::Bool(value)), NotificationHint::SoundFile(ref value) => (SOUND_FILE .to_owned(), MessageItem::Str(value.clone())),
NotificationHint::SoundName(ref value) => (SOUND_NAME .to_owned(), MessageItem::Str(value.clone())),
NotificationHint::SuppressSound(value) => (SUPPRESS_SOUND .to_owned(), MessageItem::Bool(value)),
NotificationHint::Transient(value) => (TRANSIENT .to_owned(), MessageItem::Bool(value)),
NotificationHint::X(value) => (X .to_owned(), MessageItem::Int32(value)),
NotificationHint::Y(value) => (Y .to_owned(), MessageItem::Int32(value)),
NotificationHint::Urgency(value) => (URGENCY .to_owned(), MessageItem::Byte(value as u8)),
NotificationHint::Custom(ref key, ref val) => (key .to_owned(), MessageItem::Str(val.to_owned ())),
NotificationHint::CustomInt(ref key, val) => (key .to_owned(), MessageItem::Int32(val)),
NotificationHint::Invalid => ("invalid" .to_owned(), MessageItem::Str("Invalid".to_owned()))
};
MessageItem::DictEntry(
Box::new(hint.0.into()),
Box::new(MessageItem::Variant( Box::new(hint.1) ))
)
}
}
#[cfg(all(unix, not(target_os = "macos")))]
impl<'a> From<&'a MessageItem> for NotificationHint {
fn from (item: &MessageItem) -> NotificationHint {
match item{
&MessageItem::DictEntry(ref key, ref value) if unwrap_message_str(&**key) == CATEGORY => NotificationHint::Category(unwrap_message_str(&**value)),
&MessageItem::DictEntry(ref key, ref value) if unwrap_message_str(&**key) == ACTION_ICONS => NotificationHint::ActionIcons(unwrap_message_bool(&**value)),
&MessageItem::DictEntry(ref key, ref value) if unwrap_message_str(&**key) == DESKTOP_ENTRY => NotificationHint::DesktopEntry(unwrap_message_str(&**value)),
&MessageItem::DictEntry(ref key, ref value) if unwrap_message_str(&**key) == IMAGE_PATH => NotificationHint::ImagePath(unwrap_message_str(&**value)),
&MessageItem::DictEntry(ref key, ref value) if unwrap_message_str(&**key) == RESIDENT => NotificationHint::Resident(unwrap_message_bool(&**value)),
&MessageItem::DictEntry(ref key, ref value) if unwrap_message_str(&**key) == SOUND_FILE => NotificationHint::SoundFile(unwrap_message_str(&**value)),
&MessageItem::DictEntry(ref key, ref value) if unwrap_message_str(&**key) == SOUND_NAME => NotificationHint::SoundName(unwrap_message_str(&**value)),
&MessageItem::DictEntry(ref key, ref value) if unwrap_message_str(&**key) == SUPPRESS_SOUND => NotificationHint::SuppressSound(unwrap_message_bool(&**value)),
&MessageItem::DictEntry(ref key, ref value) if unwrap_message_str(&**key) == TRANSIENT => NotificationHint::Transient(unwrap_message_bool(&**value)),
&MessageItem::DictEntry(ref key, ref value) if unwrap_message_str(&**key) == X => NotificationHint::X(unwrap_message_int(&**value)),
&MessageItem::DictEntry(ref key, ref value) if unwrap_message_str(&**key) == Y => NotificationHint::Y(unwrap_message_int(&**value)),
&MessageItem::DictEntry(ref key, ref value) if unwrap_message_str(&**key) == URGENCY => NotificationHint::Urgency(
match unwrap_message_int(&**value){
0 => NotificationUrgency::Low,
2 => NotificationUrgency::Critical,
_ => NotificationUrgency::Normal
}),
&MessageItem::DictEntry(ref key, ref value) => match try_unwrap_message_int(value) {
Some(num) => NotificationHint::CustomInt(unwrap_message_str(&**key), num),
None => NotificationHint::Custom(unwrap_message_str(&**key), unwrap_message_str(&**value)),
},
other => {println!("Invalid {:#?} ", other); NotificationHint::Invalid}
}
}
}
#[cfg(all(test, unix, not(target_os = "macos")))]
mod test {
use super::*;
use super::NotificationHint as Hint;
use NotificationUrgency::*;
use dbus::MessageItem as Item;
#[test]
fn hint_to_item() {
let category = &Hint::Category("testme".to_owned());
let item:Item= category.into();
let test_item= Item::DictEntry(
Box::new(Item::Str("category".into())),
Box::new(Item::Variant( Box::new(Item::Str("testme".into())) ))
);
assert_eq!(item, test_item);
}
#[test]
fn urgency() {
let low = &Hint::Urgency(Low);
let low_item:Item= low.into();
let test_item= Item::DictEntry(
Box::new(Item::Str("urgency".into())),
Box::new(Item::Variant( Box::new(Item::Byte(0))) ));
assert_eq!(low_item, test_item);
}
#[test]
fn simple_hint_to_item() {
let old_hint = &NotificationHint::Custom("foo".into(), "bar".into());
let item:MessageItem = old_hint.into();
let item_ref = &item;
let hint:NotificationHint = item_ref.into();
assert!(old_hint == &hint);
}
#[test]
#[cfg(all(feature = "images", unix, not(target_os = "macos")))]
fn imagedata_hint_to_item() {
let hint = &NotificationHint::ImageData(NotificationImage::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(vec![
Item::Byte(0),
Item::Byte(0),
Item::Byte(0),
],"y".into())
]))))
);
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"));
}
}