use serde::Serialize;
use std::str;
use std::str::FromStr;
use zbus::zvariant::{OwnedValue, Structure, Value};
use crate::dbus::dbusmenu_proxy::MenuLayout;
#[derive(Debug, Serialize, Clone)]
pub struct TrayMenu {
pub id: u32,
pub submenus: Vec<MenuItem>,
}
#[derive(Debug, Serialize, Clone)]
pub struct MenuItem {
pub id: i32,
pub children_display: Option<String>,
pub label: String,
pub enabled: bool,
pub visible: bool,
pub icon_name: Option<String>,
pub toggle_state: ToggleState,
pub toggle_type: ToggleType,
pub menu_type: MenuType,
pub disposition: Disposition,
pub submenu: Vec<MenuItem>,
}
impl Default for MenuItem {
fn default() -> Self {
Self {
id: 0,
children_display: None,
label: "".to_string(),
enabled: true,
visible: true,
icon_name: None,
toggle_state: ToggleState::Indeterminate,
toggle_type: ToggleType::CannotBeToggled,
menu_type: MenuType::Standard,
disposition: Disposition::Normal,
submenu: vec![],
}
}
}
#[derive(Debug, Serialize, Copy, Clone, Eq, PartialEq)]
pub enum ToggleType {
Checkmark,
Radio,
CannotBeToggled,
}
#[derive(Debug, Serialize, Copy, Clone, Eq, PartialEq)]
pub enum MenuType {
Separator,
Standard,
}
#[derive(Debug, Serialize, Copy, Clone, Eq, PartialEq)]
pub enum Disposition {
Normal,
Informative,
Warning,
Alert,
}
#[derive(Debug, Serialize, Copy, Clone, Eq, PartialEq)]
pub enum ToggleState {
On,
Off,
Indeterminate,
}
impl FromStr for MenuType {
type Err = zbus::zvariant::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"standard" => Ok(MenuType::Standard),
"separator" => Ok(MenuType::Separator),
_ => Err(zbus::zvariant::Error::IncorrectType),
}
}
}
impl FromStr for ToggleType {
type Err = zbus::zvariant::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"checkmark" => Ok(ToggleType::Checkmark),
"radio" => Ok(ToggleType::Radio),
_ => Err(zbus::zvariant::Error::IncorrectType),
}
}
}
impl FromStr for Disposition {
type Err = zbus::zvariant::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"normal" => Ok(Disposition::Normal),
"informative" => Ok(Disposition::Informative),
"warning" => Ok(Disposition::Warning),
"alert" => Ok(Disposition::Alert),
_ => Err(zbus::zvariant::Error::IncorrectType),
}
}
}
impl From<bool> for ToggleState {
fn from(value: bool) -> Self {
if value {
ToggleState::On
} else {
ToggleState::Indeterminate
}
}
}
impl TryFrom<MenuLayout> for TrayMenu {
type Error = zbus::zvariant::Error;
fn try_from(value: MenuLayout) -> Result<Self, Self::Error> {
let mut submenus = vec![];
for menu in &value.fields.submenus {
let menu = MenuItem::try_from(menu)?;
submenus.push(menu);
}
Ok(TrayMenu {
id: value.id,
submenus,
})
}
}
impl TryFrom<&OwnedValue> for MenuItem {
type Error = zbus::zvariant::Error;
fn try_from(value: &OwnedValue) -> Result<Self, Self::Error> {
let structure = value
.downcast_ref::<Structure>()
.expect("Expected a layout");
let mut fields = structure.fields().iter();
let mut menu = MenuItem::default();
if let Some(Value::I32(id)) = fields.next() {
menu.id = *id;
}
if let Some(Value::Dict(dict)) = fields.next() {
menu.children_display = dict
.get::<str, str>("children_display")?
.map(str::to_string);
menu.label = dict
.get::<str, str>("label")?
.map(|label| label.replace('_', ""))
.unwrap_or_default();
if let Some(enabled) = dict.get::<str, bool>("enabled")? {
menu.enabled = *enabled
}
if let Some(visible) = dict.get::<str, bool>("visible")? {
menu.visible = *visible;
}
menu.icon_name = dict.get::<str, str>("icon-name")?.map(str::to_string);
if let Some(disposition) = dict
.get::<str, str>("disposition")
.ok()
.flatten()
.map(Disposition::from_str)
.and_then(Result::ok)
{
menu.disposition = disposition;
}
menu.toggle_state = dict
.get::<str, bool>("toggle-state")
.ok()
.flatten()
.map(|value| ToggleState::from(*value))
.unwrap_or(ToggleState::Indeterminate);
menu.toggle_type = dict
.get::<str, str>("toggle-type")
.ok()
.flatten()
.map(ToggleType::from_str)
.and_then(Result::ok)
.unwrap_or(ToggleType::CannotBeToggled);
menu.menu_type = dict
.get::<str, str>("type")
.ok()
.flatten()
.map(MenuType::from_str)
.and_then(Result::ok)
.unwrap_or(MenuType::Standard);
};
if let Some(Value::Array(array)) = fields.next() {
let mut submenu = vec![];
for value in array.iter() {
let value = OwnedValue::from(value);
let menu = MenuItem::try_from(&value)?;
submenu.push(menu);
}
menu.submenu = submenu;
}
Ok(menu)
}
}