use std::collections::HashMap;
use std::str::FromStr;
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use zbus::zvariant::{ObjectPath, OwnedValue, Array, Structure};
type DBusProperties = HashMap<String, OwnedValue>;
struct PropsWrapper(DBusProperties);
#[derive(Serialize, Debug, Clone)]
pub struct StatusNotifierItem {
pub id: String,
pub category: Category,
pub status: Status,
pub icon_name: Option<String>,
pub icon_accessible_desc: Option<String>,
pub attention_icon_name: Option<String>,
pub title: Option<String>,
pub icon_theme_path: Option<String>,
pub icon_pixmap: Option<Vec<IconPixmap>>,
pub menu: Option<String>,
}
#[derive(Serialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
pub enum Status {
Passive,
Active,
}
impl FromStr for Status {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Passive" => Ok(Status::Active),
"Active" => Ok(Status::Passive),
other => Err(anyhow!(
"Unknown 'Status' for status notifier item {}",
other
)),
}
}
}
#[derive(Serialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
pub enum Category {
ApplicationStatus,
Communications,
SystemServices,
Hardware,
}
impl FromStr for Category {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ApplicationStatus" => Ok(Category::ApplicationStatus),
"Communications" => Ok(Category::Communications),
"SystemServices" => Ok(Category::SystemServices),
"Hardware" => Ok(Category::Hardware),
other => Err(anyhow!(
"Unknown 'Status' for status notifier item {}",
other
)),
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct IconPixmap {
pub width: i32,
pub height: i32,
pub pixels: Vec<u8>,
}
impl IconPixmap {
fn from_array(a: &Array<'_>) -> Option<Vec<Self>> {
let mut pixmaps = vec!();
a.iter().for_each(|b| {
let s = b.downcast_ref::<Structure>();
let fields = s.unwrap().fields();
let width = fields[0].downcast_ref::<i32>().unwrap();
let height = fields[1].downcast_ref::<i32>().unwrap();
let pixel_values = fields[2].downcast_ref::<Array>().unwrap().get();
let mut pixels = vec!();
pixel_values.iter().for_each(|p| {
pixels.push(p.downcast_ref::<u8>().unwrap().clone());
});
pixmaps.push(IconPixmap{width: *width, height: *height, pixels})
});
Some(pixmaps)
}
}
impl TryFrom<DBusProperties> for StatusNotifierItem {
type Error = anyhow::Error;
fn try_from(props: HashMap<String, OwnedValue>) -> anyhow::Result<Self> {
let props = PropsWrapper(props);
match props.get_string("Id") {
None => Err(anyhow!("StatusNotifier item should have an id")),
Some(id) => Ok(StatusNotifierItem {
id,
title: props.get_string("Title"),
category: props.get_category()?,
icon_name: props.get_string("IconName"),
status: props.get_status()?,
icon_accessible_desc: props.get_string("IconAccessibleDesc"),
attention_icon_name: props.get_string("AttentionIconName"),
icon_theme_path: props.get_string("IconThemePath"),
icon_pixmap: props.get_icon_pixmap(),
menu: props.get_object_path("Menu"),
}),
}
}
}
impl PropsWrapper {
fn get_string(&self, key: &str) -> Option<String> {
self.0
.get(key)
.and_then(|value| value.downcast_ref::<str>().map(|value| value.to_string()))
}
fn get_object_path(&self, key: &str) -> Option<String> {
self.0.get(key).and_then(|value| {
value
.downcast_ref::<ObjectPath>()
.map(|value| value.to_string())
})
}
fn get_category(&self) -> anyhow::Result<Category> {
self.0
.get("Category")
.and_then(|value| value.downcast_ref::<str>().map(Category::from_str))
.unwrap_or_else(|| Err(anyhow!("'Category' not found for item")))
}
fn get_status(&self) -> anyhow::Result<Status> {
self.0
.get("Status")
.and_then(|value| value.downcast_ref::<str>().map(Status::from_str))
.unwrap_or_else(|| Err(anyhow!("'Status' not found for item")))
}
fn get_icon_pixmap(&self) -> Option<Vec<IconPixmap>> {
self.0
.get("IconPixmap")
.and_then(|value|
value.downcast_ref::<Array>().map(IconPixmap::from_array) )
.unwrap_or_else(|| None )
}
}