use std::ops::Deref;
use std::sync::atomic::{self, AtomicI32};
use serde::{Deserialize, Serialize};
use zbus::zvariant::{self, OwnedValue, Type, Value, as_value::optional};
use zbus::{interface, object_server::SignalEmitter};
pub mod event_types;
pub use event_types::*;
#[derive(Type, Debug, Serialize, Deserialize, Default, OwnedValue, Value, Clone)]
#[zvariant(signature = "dict")]
pub struct MenuProperty {
#[serde(with = "optional", skip_serializing_if = "Option::is_none", default)]
pub label: Option<String>,
#[serde(with = "optional", skip_serializing_if = "Option::is_none", default)]
#[zvariant(rename = "icon-name")]
pub icon_name: Option<String>,
#[serde(with = "optional", skip_serializing_if = "Option::is_none", default)]
pub enabled: Option<bool>,
#[serde(with = "optional", skip_serializing_if = "Option::is_none", default)]
#[zvariant(rename = "toggle-type")]
pub toggle_type: Option<ToggleType>,
#[serde(with = "optional", skip_serializing_if = "Option::is_none", default)]
#[zvariant(rename = "toggle-state")]
pub toggle_state: Option<ToggleState>,
#[serde(with = "optional", skip_serializing_if = "Option::is_none", default)]
#[zvariant(rename = "children-display")]
pub children_display: Option<String>,
}
impl MenuProperty {
pub const LABEL: &str = "label";
pub const ICON_NAME: &str = "icon-name";
pub const ENABLED: &str = "enabled";
pub const TOGGLE_TYPE: &str = "toggle-type";
pub const TOGGLE_STATE: &str = "toggle-state";
pub const CHILDREN_DISPLAY: &str = "children-display";
}
impl MenuProperty {
pub fn root() -> Self {
MenuProperty {
label: Some("root".to_owned()),
children_display: Some("submenu".to_owned()),
..Default::default()
}
}
pub fn submenu(label: String) -> Self {
MenuProperty {
label: Some(label),
children_display: Some("submenu".to_owned()),
..Default::default()
}
}
}
#[derive(Type, Debug, Serialize, Deserialize, OwnedValue, Value, Clone)]
pub struct Id(i32);
static COUNT: AtomicI32 = AtomicI32::new(1);
impl Id {
const MAIN: Self = Id(0);
pub fn unique() -> Id {
Id(COUNT.fetch_add(1, atomic::Ordering::Relaxed))
}
}
impl Deref for Id {
type Target = i32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[allow(unused)]
#[derive(Debug, Clone)]
enum MenuType {
Root,
SubMenu,
Button,
}
#[derive(Debug, Clone)]
pub struct MenuUnit<Message: Clone> {
id: Id,
tp: MenuType,
pub property: MenuProperty,
pub sub_menus: Vec<MenuUnit<Message>>,
pub message: Option<Message>,
}
#[derive(Debug, Clone, Default)]
pub struct ButtonOptions {
pub label: String,
pub enabled: bool,
pub icon_name: String,
}
impl<Message: Clone> From<MenuUnit<Message>> for MenuItem {
fn from(value: MenuUnit<Message>) -> Self {
let mut output = MenuItem {
id: value.id,
property: value.property,
sub_menus: vec![],
};
for sub_menu in value.sub_menus {
output = output.push_sub_menu(sub_menu.into());
}
output
}
}
impl<Message: Clone> MenuUnit<Message> {
pub fn button(
ButtonOptions {
label,
enabled,
icon_name,
}: ButtonOptions,
message: Message,
) -> Self {
Self {
tp: MenuType::Button,
id: Id::unique(),
property: MenuProperty {
label: Some(label),
enabled: Some(enabled),
icon_name: Some(icon_name),
..Default::default()
},
sub_menus: vec![],
message: Some(message),
}
}
pub fn root() -> Self {
Self {
id: Id::MAIN,
tp: MenuType::Root,
property: MenuProperty::root(),
sub_menus: vec![],
message: None,
}
}
pub fn sub_menu(label: String) -> Self {
Self {
id: Id::unique(),
tp: MenuType::SubMenu,
property: MenuProperty::submenu(label),
sub_menus: vec![],
message: None,
}
}
pub fn push_sub_menu(mut self, menu: Self) -> Self {
self.sub_menus.push(menu);
self
}
pub fn find_menu_by_id(&self, id: i32) -> Option<&Self> {
if *self.id == id {
return Some(self);
}
for menu in &self.sub_menus {
if let Some(menu) = menu.find_menu_by_id(id) {
return Some(menu);
}
}
None
}
}
#[derive(Type, Debug, Serialize, Deserialize, OwnedValue, Value, Clone)]
#[zvariant(signature = "(ia{sv}av)")]
pub struct MenuItem {
pub id: Id,
pub property: MenuProperty,
pub sub_menus: Vec<zvariant::OwnedValue>,
}
#[derive(Clone, PartialEq, Type, Serialize, Deserialize, OwnedValue, Value, Debug, Default)]
#[zvariant(signature = "s", rename_all = "lowercase")]
pub enum MenuStatus {
#[default]
Normal,
Notice,
Disabled,
}
impl Default for MenuItem {
fn default() -> Self {
MenuItem {
id: Id::unique(),
property: MenuProperty::root(),
sub_menus: vec![],
}
}
}
impl MenuItem {
pub fn new(property: MenuProperty) -> Self {
MenuItem {
id: Id::unique(),
property,
sub_menus: vec![],
}
}
#[allow(unused)]
#[allow(clippy::only_used_in_recursion)]
pub fn get_filiter(
&self,
parent_id: i32,
recursion_depth: i32,
property_names: &[&str],
) -> Option<MenuItem> {
if *self.id == parent_id {
let mut new_menu = MenuItem {
id: self.id.clone(),
property: self.property.clone(),
sub_menus: vec![],
};
let next_reversion_depth = recursion_depth - 1;
if next_reversion_depth != 0 {
for menu in self.sub_menus.as_slice() {
let menu: MenuItem = menu.clone().try_into().unwrap();
let next_menu = menu.filiter(next_reversion_depth, property_names);
new_menu = new_menu.push_sub_menu(next_menu);
}
}
return Some(new_menu);
}
None
}
#[allow(unused)]
#[allow(clippy::only_used_in_recursion)]
fn filiter(&self, recursion_depth: i32, property_names: &[&str]) -> MenuItem {
let mut new_menu = MenuItem {
id: self.id.clone(),
property: self.property.clone(),
sub_menus: vec![],
};
let next_reversion_depth = recursion_depth - 1;
for menu in self.sub_menus.as_slice() {
let menu: MenuItem = menu.clone().try_into().unwrap();
let next_menu = menu.filiter(next_reversion_depth, property_names);
new_menu = new_menu.push_sub_menu(next_menu);
}
new_menu
}
pub fn push_sub_menu(mut self, menu: MenuItem) -> Self {
self.sub_menus.push(OwnedValue::try_from(menu).unwrap());
self
}
pub fn get_property(&self, id: i32, name: String) -> Option<PropertyItem> {
if *self.id == id {
return Some(PropertyItem {
id,
item: self.property.clone(),
});
}
let sub_menus: Vec<MenuItem> = self
.sub_menus
.iter()
.map(|submenu| submenu.clone().try_into().unwrap())
.collect();
for sub_menu in sub_menus {
let property = sub_menu.get_property(id, name.clone());
if property.is_some() {
return property;
}
}
None
}
pub fn get_property_groups(
&self,
ids: Vec<i32>,
_property_names: Vec<String>,
) -> Vec<PropertyItem> {
let mut output = vec![];
for id in ids {
if let Some(property) = self.get_property(id, "".to_string()) {
output.push(property);
}
}
output
}
}
#[derive(Type, Debug, Default, Serialize, Deserialize)]
pub struct PropertyItem {
pub id: i32,
pub item: MenuProperty,
}
pub trait DBusMenuItem {
type State;
type Message: Clone;
fn boot(&self) -> Self::State;
fn menu(&self, state: &Self::State) -> MenuUnit<Self::Message>;
fn revision(&self, state: &Self::State) -> u32;
#[allow(unused)]
fn about_to_show(&self, state: &mut Self::State, id: i32) -> zbus::fdo::Result<bool> {
Err(zbus::fdo::Error::Failed("Unimplemented".to_string()))
}
#[allow(unused)]
fn about_to_show_group(
&self,
state: &mut Self::State,
ids: Vec<i32>,
) -> zbus::fdo::Result<(Vec<i32>, Vec<i32>)> {
Err(zbus::fdo::Error::Failed("Unimplemented".to_string()))
}
fn status(&self, _state: &Self::State) -> zbus::fdo::Result<MenuStatus> {
Ok(MenuStatus::Normal)
}
#[allow(unused)]
fn on_clicked(
&self,
state: &mut Self::State,
message: Self::Message,
timestamp: u32,
) -> EventUpdate {
EventUpdate::None
}
#[allow(unused)]
fn text_direction(&self, state: &Self::State) -> TextDirection {
TextDirection::Inherit
}
#[allow(unused)]
fn icon_theme_path(&self, state: &Self::State) -> Vec<String> {
vec![]
}
}
pub struct DBusMenuInstance<Menu: DBusMenuItem> {
pub(crate) program: Menu,
pub(crate) state: Menu::State,
}
pub trait DBusMenuBootFn<State> {
fn boot(&self) -> State;
}
impl<T, State> DBusMenuBootFn<State> for T
where
T: Fn() -> State,
{
fn boot(&self) -> State {
self()
}
}
pub trait MenuStatusFn<State> {
fn status(&self, state: &State) -> MenuStatus;
}
impl<T, State> MenuStatusFn<State> for T
where
T: Fn(&State) -> MenuStatus,
{
fn status(&self, state: &State) -> MenuStatus {
self(state)
}
}
pub trait AboutToShowFn<State> {
fn about_to_show(&self, state: &mut State, id: i32) -> bool;
}
impl<T, State> AboutToShowFn<State> for T
where
T: Fn(&mut State, i32) -> bool,
{
fn about_to_show(&self, state: &mut State, id: i32) -> bool {
self(state, id)
}
}
#[derive(Debug)]
pub enum EventUpdate {
None,
UpdateCurrent,
UpdateAll,
}
pub trait RevisionFn<State> {
fn revision(&self, state: &State) -> u32;
}
impl<State, F> RevisionFn<State> for F
where
F: Fn(&State) -> u32,
{
fn revision(&self, state: &State) -> u32 {
self(state)
}
}
impl<State> RevisionFn<State> for u32 {
fn revision(&self, _state: &State) -> u32 {
*self
}
}
pub trait MenuFn<State, Message: Clone> {
fn menu(&self, state: &State) -> MenuUnit<Message>;
}
impl<State, Message: Clone, F> MenuFn<State, Message> for F
where
F: Fn(&State) -> MenuUnit<Message>,
{
fn menu(&self, state: &State) -> MenuUnit<Message> {
self(state)
}
}
pub trait OnClickedFn<State, Message> {
fn on_clicked(&self, state: &mut State, message: Message, timestamp: u32) -> EventUpdate;
}
impl<T, State, Message> OnClickedFn<State, Message> for T
where
T: Fn(&mut State, Message, u32) -> EventUpdate,
{
fn on_clicked(&self, state: &mut State, message: Message, timestamp: u32) -> EventUpdate {
self(state, message, timestamp)
}
}
pub trait TextDirectionFn<State> {
fn text_direction(&self, state: &State) -> TextDirection;
}
impl<State> TextDirectionFn<State> for TextDirection {
fn text_direction(&self, _state: &State) -> TextDirection {
*self
}
}
impl<T, State> TextDirectionFn<State> for T
where
T: Fn(&State) -> TextDirection,
{
fn text_direction(&self, state: &State) -> TextDirection {
self(state)
}
}
pub trait IconThemePathFn<State> {
fn icon_theme_path(&self, state: &State) -> Vec<String>;
}
impl<State> IconThemePathFn<State> for Vec<String> {
fn icon_theme_path(&self, _state: &State) -> Vec<String> {
self.clone()
}
}
impl<T, State> IconThemePathFn<State> for T
where
T: Fn(&State) -> Vec<String>,
{
fn icon_theme_path(&self, state: &State) -> Vec<String> {
self(state)
}
}
pub trait AboutToShowGroupFn<State> {
fn about_to_show_group(
&self,
state: &mut State,
ids: Vec<i32>,
) -> zbus::fdo::Result<(Vec<i32>, Vec<i32>)>;
}
impl<T, State> AboutToShowGroupFn<State> for T
where
T: Fn(&mut State, Vec<i32>) -> zbus::fdo::Result<(Vec<i32>, Vec<i32>)>,
{
fn about_to_show_group(
&self,
state: &mut State,
ids: Vec<i32>,
) -> zbus::fdo::Result<(Vec<i32>, Vec<i32>)> {
self(state, ids)
}
}
#[interface(name = "com.canonical.dbusmenu")]
impl<Menu: DBusMenuItem> DBusMenuInstance<Menu>
where
Menu: Send + Sync + 'static,
Menu::State: 'static + Send + Sync,
Menu::Message: 'static + Send + Sync + Clone,
{
fn about_to_show(&mut self, id: i32) -> zbus::fdo::Result<bool> {
self.program.about_to_show(&mut self.state, id)
}
fn about_to_show_group(&mut self, ids: Vec<i32>) -> zbus::fdo::Result<(Vec<i32>, Vec<i32>)> {
self.program.about_to_show_group(&mut self.state, ids)
}
fn get_layout(
&mut self,
parent_id: i32,
recursion_depth: i32,
property_names: Vec<String>,
) -> zbus::fdo::Result<(u32, MenuItem)> {
let property_names: Vec<&str> = property_names.iter().map(|name| name.as_str()).collect();
let menuitem: MenuItem = self.program.menu(&self.state).into();
Ok((
self.program.revision(&self.state),
menuitem
.get_filiter(parent_id, recursion_depth, &property_names)
.ok_or(zbus::fdo::Error::Failed("UnFounded".to_string()))?,
))
}
fn get_group_properties(
&mut self,
ids: Vec<i32>,
property_names: Vec<String>,
) -> zbus::fdo::Result<Vec<PropertyItem>> {
let menuitem: MenuItem = self.program.menu(&self.state).into();
Ok(menuitem.get_property_groups(ids, property_names))
}
fn get_property(&mut self, id: i32, name: String) -> zbus::fdo::Result<PropertyItem> {
let menuitem: MenuItem = self.program.menu(&self.state).into();
menuitem
.get_property(id, name)
.ok_or(zbus::fdo::Error::Failed("Unfounded".to_string()))
}
#[zbus(property)]
fn version(&self) -> u32 {
2
}
#[zbus(property)]
fn status(&self) -> zbus::fdo::Result<MenuStatus> {
self.program.status(&self.state)
}
async fn event(
&mut self,
id: i32,
event_id: String,
_data: zbus::zvariant::OwnedValue,
timestamp: u32,
#[zbus(object_server)] server: &zbus::ObjectServer,
) -> zbus::fdo::Result<()> {
let menu = self.program.menu(&self.state);
let Some(button) = menu.find_menu_by_id(id) else {
return Ok(());
};
let need_update = match event_id.as_str() {
"clicked" => {
if !matches!(button.tp, MenuType::Button) {
return Ok(());
}
self.program
.on_clicked(&mut self.state, button.message.clone().unwrap(), timestamp)
}
_ => EventUpdate::None,
};
let revision = self.program.revision(&self.state);
match need_update {
EventUpdate::UpdateCurrent => {
let iface_rf = server
.interface::<_, DBusMenuInstance<Menu>>("/MenuBar")
.await?;
let _ = DBusMenuInstance::<Menu>::layout_updated(
iface_rf.signal_emitter(),
revision,
id,
)
.await;
}
EventUpdate::UpdateAll => {
let iface_rf = server
.interface::<_, DBusMenuInstance<Menu>>("/MenuBar")
.await?;
let _ = DBusMenuInstance::<Menu>::layout_updated(
iface_rf.signal_emitter(),
revision,
*Id::MAIN,
)
.await;
}
_ => {}
}
Ok(())
}
async fn event_group(
&mut self,
events: Vec<(i32, String, zbus::zvariant::OwnedValue, u32)>,
#[zbus(object_server)] server: &zbus::ObjectServer,
) -> zbus::fdo::Result<Vec<i32>> {
let mut output = vec![];
let mut update_all = false;
let mut update_parents: Vec<i32> = vec![];
for (id, event_id, _data, timestamp) in events {
let menu = self.program.menu(&self.state);
let Some(button) = menu.find_menu_by_id(id) else {
continue;
};
let need_update = match event_id.as_str() {
"clicked" => {
if !matches!(button.tp, MenuType::Button) {
continue;
}
self.program.on_clicked(
&mut self.state,
button.message.clone().unwrap(),
timestamp,
)
}
_ => {
continue;
}
};
match need_update {
EventUpdate::None => {
continue;
}
EventUpdate::UpdateAll => {
update_all = true;
}
EventUpdate::UpdateCurrent => {
update_parents.push(*menu.id);
}
};
output.push(id);
}
let revision = self.program.revision(&self.state);
if update_all {
let iface_rf = server
.interface::<_, DBusMenuInstance<Menu>>("/MenuBar")
.await?;
let _ = DBusMenuInstance::<Menu>::layout_updated(
iface_rf.signal_emitter(),
revision,
*Id::MAIN,
)
.await;
} else {
for id in update_parents {
let iface_rf = server
.interface::<_, DBusMenuInstance<Menu>>("/MenuBar")
.await?;
let _ = DBusMenuInstance::<Menu>::layout_updated(
iface_rf.signal_emitter(),
revision,
id,
)
.await;
}
}
Ok(output)
}
#[zbus(property)]
fn text_direction(&self) -> TextDirection {
self.program.text_direction(&self.state)
}
#[zbus(property)]
fn icon_theme_path(&self) -> Vec<String> {
self.program.icon_theme_path(&self.state)
}
#[zbus(signal)]
pub async fn item_activation_requested(
ctxt: &SignalEmitter<'_>,
id: i32,
timestamp: u32,
) -> zbus::Result<()>;
#[zbus(signal)]
pub async fn items_properties_updated(
ctxt: &SignalEmitter<'_>,
updated_props: Vec<(i32, MenuProperty)>,
removed_props: Vec<(i32, Vec<&str>)>,
) -> zbus::Result<()>;
#[zbus(signal)]
pub async fn layout_updated(
ctxt: &SignalEmitter<'_>,
revision: u32,
parent: i32,
) -> zbus::Result<()>;
}