mirror of
https://github.com/JakeStanger/ironbar.git
synced 2024-11-22 05:34:35 +03:00
refactor(tray): switch over to libdbusmenu-gtk3
Also adds tooltips
This commit is contained in:
parent
ff3f541183
commit
30de23dc64
1
.github/scripts/ubuntu_setup.sh
vendored
1
.github/scripts/ubuntu_setup.sh
vendored
@ -17,5 +17,6 @@ $SUDO apt-get update && $SUDO apt-get install --assume-yes \
|
|||||||
libssl-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
libssl-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||||
libgtk-3-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
libgtk-3-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||||
libgtk-layer-shell-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
libgtk-layer-shell-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||||
|
libdbusmenu-gtk3-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||||
libpulse-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
libpulse-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||||
libluajit-5.1-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH}
|
libluajit-5.1-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH}
|
||||||
|
34
Cargo.lock
generated
34
Cargo.lock
generated
@ -691,6 +691,34 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dbusmenu-glib-sys"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ff9ed40330718c94342b953c997ac19d840db07a7710fe35b45a5d3a3a1d6eb"
|
||||||
|
dependencies = [
|
||||||
|
"glib-sys",
|
||||||
|
"gobject-sys",
|
||||||
|
"libc",
|
||||||
|
"system-deps",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dbusmenu-gtk3-sys"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f30ba5f8aec0e38a84c579bc8ee3db6f6417b201e729fdd96a23d1f61cb6eca"
|
||||||
|
dependencies = [
|
||||||
|
"dbusmenu-glib-sys",
|
||||||
|
"gdk-pixbuf-sys",
|
||||||
|
"gdk-sys",
|
||||||
|
"glib-sys",
|
||||||
|
"gobject-sys",
|
||||||
|
"gtk-sys",
|
||||||
|
"libc",
|
||||||
|
"system-deps",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
@ -3229,10 +3257,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-tray"
|
name = "system-tray"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "66066cf85f8a4985ae5a40ca4387036e4f870d95fbf113ffaab714796785d0f2"
|
checksum = "c5eff7a9f41bc02826920bf9da8b03fb74cb9c55bac250b4bc470af12d980285"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"dbusmenu-gtk3-sys",
|
||||||
|
"gtk",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.0",
|
"thiserror 2.0.0",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -148,7 +148,7 @@ futures-signals = { version = "0.3.34", optional = true }
|
|||||||
sysinfo = { version = "0.29.11", optional = true }
|
sysinfo = { version = "0.29.11", optional = true }
|
||||||
|
|
||||||
# tray
|
# tray
|
||||||
system-tray = { version = "0.3.0", optional = true }
|
system-tray = { version = "0.4.0", features = ["dbusmenu-gtk3"], optional = true }
|
||||||
png = { version = "0.17.14", optional = true }
|
png = { version = "0.17.14", optional = true }
|
||||||
|
|
||||||
# upower
|
# upower
|
||||||
|
@ -22,6 +22,8 @@ You also need rust; only the latest stable version is supported.
|
|||||||
pacman -S gtk3 gtk-layer-shell
|
pacman -S gtk3 gtk-layer-shell
|
||||||
# for http support
|
# for http support
|
||||||
pacman -S openssl
|
pacman -S openssl
|
||||||
|
# for tray support
|
||||||
|
pacman -S libdbusmenu-gtk3
|
||||||
# for volume support
|
# for volume support
|
||||||
pacman -S libpulse
|
pacman -S libpulse
|
||||||
# for lua/cairo support
|
# for lua/cairo support
|
||||||
@ -34,6 +36,8 @@ pacman -S luajit lua51-lgi
|
|||||||
apt install build-essential libgtk-3-dev libgtk-layer-shell-dev
|
apt install build-essential libgtk-3-dev libgtk-layer-shell-dev
|
||||||
# for http support
|
# for http support
|
||||||
apt install libssl-dev
|
apt install libssl-dev
|
||||||
|
# for tray support
|
||||||
|
apt install libdbusmenu-gtk3-dev
|
||||||
# for volume support
|
# for volume support
|
||||||
apt install libpulse-dev
|
apt install libpulse-dev
|
||||||
# for lua/cairo support
|
# for lua/cairo support
|
||||||
@ -46,6 +50,8 @@ apt install luajit-dev lua-lgi
|
|||||||
dnf install gtk3-devel gtk-layer-shell-devel
|
dnf install gtk3-devel gtk-layer-shell-devel
|
||||||
# for http support
|
# for http support
|
||||||
dnf install openssl-devel
|
dnf install openssl-devel
|
||||||
|
# for tray support
|
||||||
|
dnf install libdbusmenu-gtk3-devel
|
||||||
# for volume support
|
# for volume support
|
||||||
dnf install pulseaudio-libs-devel
|
dnf install pulseaudio-libs-devel
|
||||||
# for lua/cairo support
|
# for lua/cairo support
|
||||||
|
@ -129,6 +129,7 @@
|
|||||||
hicolor-icon-theme
|
hicolor-icon-theme
|
||||||
gsettings-desktop-schemas
|
gsettings-desktop-schemas
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
|
libdbusmenu-gtk3
|
||||||
libpulseaudio
|
libpulseaudio
|
||||||
luajit
|
luajit
|
||||||
luajitPackages.lgi
|
luajitPackages.lgi
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
gtk-layer-shell,
|
gtk-layer-shell,
|
||||||
gnome,
|
gnome,
|
||||||
libxkbcommon,
|
libxkbcommon,
|
||||||
|
libdbusmenu-gtk3,
|
||||||
libpulseaudio,
|
libpulseaudio,
|
||||||
openssl,
|
openssl,
|
||||||
luajit,
|
luajit,
|
||||||
@ -54,9 +55,10 @@
|
|||||||
hicolor-icon-theme
|
hicolor-icon-theme
|
||||||
gsettings-desktop-schemas
|
gsettings-desktop-schemas
|
||||||
libxkbcommon ]
|
libxkbcommon ]
|
||||||
++ (if hasFeature "http" then [ openssl ] else [])
|
++ lib.optionals (hasFeature "http") [ openssl ]
|
||||||
++ (if hasFeature "volume" then [ libpulseaudio ] else [])
|
++ lib.optionals (hasFeature "tray") [ libdbusmenu-gtk3 ]
|
||||||
++ (if hasFeature "cairo" then [ luajit ] else []);
|
++ lib.optionals (hasFeature "volume")[ libpulseaudio ]
|
||||||
|
++ lib.optionals (hasFeature "cairo") [ luajit ];
|
||||||
|
|
||||||
propagatedBuildInputs = [ gtk3 ];
|
propagatedBuildInputs = [ gtk3 ];
|
||||||
|
|
||||||
@ -72,10 +74,10 @@
|
|||||||
# gtk-launch
|
# gtk-launch
|
||||||
--suffix PATH : "${lib.makeBinPath [ gtk3 ]}"
|
--suffix PATH : "${lib.makeBinPath [ gtk3 ]}"
|
||||||
''
|
''
|
||||||
+ (if hasFeature "cairo" then ''
|
+ lib.optionalString (hasFeature "cairo") ''
|
||||||
--prefix LUA_PATH : "./?.lua;${lgi}/share/lua/5.1/?.lua;${lgi}/share/lua/5.1/?/init.lua;${luajit}/share/lua/5.1/\?.lua;${luajit}/share/lua/5.1/?/init.lua"
|
--prefix LUA_PATH : "./?.lua;${lgi}/share/lua/5.1/?.lua;${lgi}/share/lua/5.1/?/init.lua;${luajit}/share/lua/5.1/\?.lua;${luajit}/share/lua/5.1/?/init.lua"
|
||||||
--prefix LUA_CPATH : "./?.so;${lgi}/lib/lua/5.1/?.so;${luajit}/lib/lua/5.1/?.so;${luajit}/lib/lua/5.1/loadall.so"
|
--prefix LUA_CPATH : "./?.so;${lgi}/lib/lua/5.1/?.so;${luajit}/lib/lua/5.1/?.so;${luajit}/lib/lua/5.1/loadall.so"
|
||||||
'' else "");
|
'';
|
||||||
|
|
||||||
preFixup = ''
|
preFixup = ''
|
||||||
gappsWrapperArgs+=(
|
gappsWrapperArgs+=(
|
||||||
|
@ -9,6 +9,7 @@ pkgs.mkShell {
|
|||||||
gtk-layer-shell
|
gtk-layer-shell
|
||||||
gcc
|
gcc
|
||||||
openssl
|
openssl
|
||||||
|
libdbusmenu-gtk3
|
||||||
libpulseaudio
|
libpulseaudio
|
||||||
luajit
|
luajit
|
||||||
luajitPackages.lgi
|
luajitPackages.lgi
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
use system_tray::menu::{MenuItem, ToggleState};
|
|
||||||
|
|
||||||
/// Diff change type and associated info.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Diff {
|
|
||||||
Add(MenuItem),
|
|
||||||
Update(i32, MenuItemDiff),
|
|
||||||
Remove(i32),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Diff info to be applied to an existing menu item as an update.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct MenuItemDiff {
|
|
||||||
/// Text of the item,
|
|
||||||
pub label: Option<Option<String>>,
|
|
||||||
/// Whether the item can be activated or not.
|
|
||||||
pub enabled: Option<bool>,
|
|
||||||
/// True if the item is visible in the menu.
|
|
||||||
pub visible: Option<bool>,
|
|
||||||
/// Icon name of the item, following the freedesktop.org icon spec.
|
|
||||||
pub icon_name: Option<Option<String>>,
|
|
||||||
/// PNG icon data.
|
|
||||||
pub icon_data: Option<Option<Vec<u8>>>,
|
|
||||||
/// Describe the current state of a "togglable" item. Can be one of:
|
|
||||||
/// - Some(true): on
|
|
||||||
/// - Some(false): off
|
|
||||||
/// - None: indeterminate
|
|
||||||
pub toggle_state: Option<ToggleState>,
|
|
||||||
/// A submenu for this item, typically this would ve revealed to the user by hovering the current item
|
|
||||||
pub submenu: Vec<Diff>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MenuItemDiff {
|
|
||||||
fn new(old: &MenuItem, new: &MenuItem) -> Self {
|
|
||||||
macro_rules! diff {
|
|
||||||
($field:ident) => {
|
|
||||||
if old.$field == new.$field {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(new.$field)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(&$field:ident) => {
|
|
||||||
if &old.$field == &new.$field {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(new.$field.clone())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
label: diff!(&label),
|
|
||||||
enabled: diff!(enabled),
|
|
||||||
visible: diff!(visible),
|
|
||||||
icon_name: diff!(&icon_name),
|
|
||||||
icon_data: diff!(&icon_data),
|
|
||||||
toggle_state: diff!(toggle_state),
|
|
||||||
submenu: get_diffs(&old.submenu, &new.submenu),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this diff contains any changes
|
|
||||||
fn has_diff(&self) -> bool {
|
|
||||||
self.label.is_some()
|
|
||||||
|| self.enabled.is_some()
|
|
||||||
|| self.visible.is_some()
|
|
||||||
// || self.icon_name.is_some()
|
|
||||||
|| self.toggle_state.is_some()
|
|
||||||
|| !self.submenu.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a diff set between old and new state.
|
|
||||||
pub fn get_diffs(old: &[MenuItem], new: &[MenuItem]) -> Vec<Diff> {
|
|
||||||
let mut diffs = vec![];
|
|
||||||
|
|
||||||
for new_item in new {
|
|
||||||
let old_item = old.iter().find(|&item| item.id == new_item.id);
|
|
||||||
|
|
||||||
let diff = match old_item {
|
|
||||||
Some(old_item) => {
|
|
||||||
let item_diff = MenuItemDiff::new(old_item, new_item);
|
|
||||||
if item_diff.has_diff() {
|
|
||||||
Some(Diff::Update(old_item.id, item_diff))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Some(Diff::Add(new_item.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(diff) = diff {
|
|
||||||
diffs.push(diff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for old_item in old {
|
|
||||||
let new_item = new.iter().find(|&item| item.id == old_item.id);
|
|
||||||
if new_item.is_none() {
|
|
||||||
diffs.push(Diff::Remove(old_item.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
diffs
|
|
||||||
}
|
|
@ -1,119 +1,34 @@
|
|||||||
use super::diff::{Diff, MenuItemDiff};
|
|
||||||
use crate::image::ImageProvider;
|
|
||||||
use crate::modules::tray::icon::PngData;
|
|
||||||
use crate::{spawn, try_send};
|
|
||||||
use glib::Propagation;
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{
|
use gtk::{Image, Label, MenuItem};
|
||||||
CheckMenuItem, Container, IconTheme, Image, Label, Menu, MenuItem, Orientation,
|
use system_tray::item::{IconPixmap, StatusNotifierItem, Tooltip};
|
||||||
SeparatorMenuItem,
|
|
||||||
};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use system_tray::client::ActivateRequest;
|
|
||||||
use system_tray::item::{IconPixmap, StatusNotifierItem};
|
|
||||||
use system_tray::menu::{MenuItem as MenuItemInfo, MenuType, ToggleState, ToggleType};
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use tracing::{error, warn};
|
|
||||||
|
|
||||||
/// Calls a method on the underlying widget,
|
|
||||||
/// passing in a single argument.
|
|
||||||
///
|
|
||||||
/// This is useful to avoid matching on
|
|
||||||
/// `TrayMenuWidget` constantly.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```rust
|
|
||||||
/// call!(container, add, my_widget)
|
|
||||||
/// ```
|
|
||||||
/// is the same as:
|
|
||||||
/// ```
|
|
||||||
/// match &my_widget {
|
|
||||||
/// TrayMenuWidget::Separator(w) => {
|
|
||||||
/// container.add(w);
|
|
||||||
/// }
|
|
||||||
/// TrayMenuWidget::Standard(w) => {
|
|
||||||
/// container.add(w);
|
|
||||||
/// }
|
|
||||||
/// TrayMenuWidget::Checkbox(w) => {
|
|
||||||
/// container.add(w);
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
macro_rules! call {
|
|
||||||
($parent:expr, $method:ident, $child:expr) => {
|
|
||||||
match &$child {
|
|
||||||
TrayMenuWidget::Separator(w) => {
|
|
||||||
$parent.$method(w);
|
|
||||||
}
|
|
||||||
TrayMenuWidget::Standard(w) => {
|
|
||||||
$parent.$method(w);
|
|
||||||
}
|
|
||||||
TrayMenuWidget::Checkbox(w) => {
|
|
||||||
$parent.$method(w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Main tray icon to show on the bar
|
/// Main tray icon to show on the bar
|
||||||
pub(crate) struct TrayMenu {
|
pub(crate) struct TrayMenu {
|
||||||
pub widget: MenuItem,
|
pub widget: MenuItem,
|
||||||
menu_widget: Menu,
|
menu_widget: Option<system_tray::gtk_menu::Menu>,
|
||||||
image_widget: Option<Image>,
|
image_widget: Option<Image>,
|
||||||
label_widget: Option<Label>,
|
label_widget: Option<Label>,
|
||||||
|
|
||||||
menu: HashMap<i32, TrayMenuItem>,
|
|
||||||
state: Vec<MenuItemInfo>,
|
|
||||||
|
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub icon_name: Option<String>,
|
pub icon_name: Option<String>,
|
||||||
pub icon_theme_path: Option<String>,
|
pub icon_theme_path: Option<String>,
|
||||||
pub icon_pixmap: Option<Vec<IconPixmap>>,
|
pub icon_pixmap: Option<Vec<IconPixmap>>,
|
||||||
|
|
||||||
tx: mpsc::Sender<i32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrayMenu {
|
impl TrayMenu {
|
||||||
pub fn new(
|
pub fn new(item: StatusNotifierItem) -> Self {
|
||||||
tx: mpsc::Sender<ActivateRequest>,
|
|
||||||
address: String,
|
|
||||||
item: StatusNotifierItem,
|
|
||||||
) -> Self {
|
|
||||||
let widget = MenuItem::new();
|
let widget = MenuItem::new();
|
||||||
widget.style_context().add_class("item");
|
widget.style_context().add_class("item");
|
||||||
|
|
||||||
let (item_tx, mut item_rx) = mpsc::channel(8);
|
|
||||||
|
|
||||||
if let Some(menu) = item.menu {
|
|
||||||
spawn(async move {
|
|
||||||
while let Some(id) = item_rx.recv().await {
|
|
||||||
try_send!(
|
|
||||||
tx,
|
|
||||||
ActivateRequest {
|
|
||||||
submenu_id: id,
|
|
||||||
menu_path: menu.clone(),
|
|
||||||
address: address.clone(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let menu = Menu::new();
|
|
||||||
widget.set_submenu(Some(&menu));
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
widget,
|
widget,
|
||||||
menu_widget: menu,
|
menu_widget: None,
|
||||||
image_widget: None,
|
image_widget: None,
|
||||||
label_widget: None,
|
label_widget: None,
|
||||||
state: vec![],
|
|
||||||
title: item.title,
|
title: item.title,
|
||||||
icon_name: item.icon_name,
|
icon_name: item.icon_name,
|
||||||
icon_theme_path: item.icon_theme_path,
|
icon_theme_path: item.icon_theme_path,
|
||||||
icon_pixmap: item.icon_pixmap,
|
icon_pixmap: item.icon_pixmap,
|
||||||
menu: HashMap::new(),
|
|
||||||
tx: item_tx,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,42 +74,10 @@ impl TrayMenu {
|
|||||||
image.show();
|
image.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies a diff set to the submenu.
|
|
||||||
pub fn apply_diffs(&mut self, diffs: Vec<Diff>) {
|
|
||||||
for diff in diffs {
|
|
||||||
match diff {
|
|
||||||
Diff::Add(info) => {
|
|
||||||
let item = TrayMenuItem::new(&info, self.tx.clone());
|
|
||||||
call!(self.menu_widget, add, item.widget);
|
|
||||||
self.menu.insert(item.id, item);
|
|
||||||
// self.widget.show_all();
|
|
||||||
}
|
|
||||||
Diff::Update(id, info) => {
|
|
||||||
if let Some(item) = self.menu.get_mut(&id) {
|
|
||||||
item.apply_diff(info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Diff::Remove(id) => {
|
|
||||||
if let Some(item) = self.menu.remove(&id) {
|
|
||||||
call!(self.menu_widget, remove, item.widget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn label_widget(&self) -> Option<&Label> {
|
pub fn label_widget(&self) -> Option<&Label> {
|
||||||
self.label_widget.as_ref()
|
self.label_widget.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(&self) -> &[MenuItemInfo] {
|
|
||||||
&self.state
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_state(&mut self, state: Vec<MenuItemInfo>) {
|
|
||||||
self.state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn icon_name(&self) -> Option<&String> {
|
pub fn icon_name(&self) -> Option<&String> {
|
||||||
self.icon_name.as_ref()
|
self.icon_name.as_ref()
|
||||||
}
|
}
|
||||||
@ -202,205 +85,21 @@ impl TrayMenu {
|
|||||||
pub fn set_icon_name(&mut self, icon_name: Option<String>) {
|
pub fn set_icon_name(&mut self, icon_name: Option<String>) {
|
||||||
self.icon_name = icon_name;
|
self.icon_name = icon_name;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub fn set_tooltip(&self, tooltip: Option<Tooltip>) {
|
||||||
struct TrayMenuItem {
|
let title = tooltip.map(|t| t.title);
|
||||||
id: i32,
|
|
||||||
widget: TrayMenuWidget,
|
|
||||||
menu_widget: Menu,
|
|
||||||
submenu: HashMap<i32, TrayMenuItem>,
|
|
||||||
tx: mpsc::Sender<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
if let Some(widget) = &self.image_widget {
|
||||||
enum TrayMenuWidget {
|
widget.set_tooltip_text(title.as_deref());
|
||||||
Separator(SeparatorMenuItem),
|
}
|
||||||
Standard(MenuItem),
|
|
||||||
Checkbox(CheckMenuItem),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_item<W>(widget: &W, info: &MenuItemInfo)
|
if let Some(widget) = &self.label_widget {
|
||||||
where
|
widget.set_tooltip_text(title.as_deref());
|
||||||
W: IsA<MenuItem> + IsA<Container>,
|
|
||||||
{
|
|
||||||
let container = gtk::Box::new(Orientation::Horizontal, 10);
|
|
||||||
widget.add(&container);
|
|
||||||
|
|
||||||
if let Some(icon) = &info.icon_name {
|
|
||||||
// TODO: Get theme here
|
|
||||||
let image = Image::new();
|
|
||||||
match ImageProvider::parse(icon, &IconTheme::new(), true, 24)
|
|
||||||
.map(|provider| provider.load_into_image(image.clone()))
|
|
||||||
{
|
|
||||||
Some(Ok(())) => container.add(&image),
|
|
||||||
_ => warn!("Failed to load icon: {icon}"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(icon_data) = &info.icon_data {
|
pub fn set_menu_widget(&mut self, menu: system_tray::gtk_menu::Menu) {
|
||||||
match Image::try_from(PngData(icon_data.as_slice())) {
|
self.widget.set_submenu(Some(&menu));
|
||||||
Ok(image) => container.add(&image),
|
self.menu_widget = Some(menu);
|
||||||
Err(err) => error!("{err:?}"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let label = Label::new(info.label.as_deref());
|
|
||||||
container.add(&label);
|
|
||||||
|
|
||||||
container.show_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TrayMenuItem {
|
|
||||||
fn new(info: &MenuItemInfo, tx: mpsc::Sender<i32>) -> Self {
|
|
||||||
let mut submenu = HashMap::new();
|
|
||||||
let menu = Menu::new();
|
|
||||||
|
|
||||||
macro_rules! add_submenu {
|
|
||||||
($menu:expr, $widget:expr) => {
|
|
||||||
if !info.submenu.is_empty() {
|
|
||||||
for sub_item in &info.submenu {
|
|
||||||
let sub_item = TrayMenuItem::new(sub_item, tx.clone());
|
|
||||||
call!($menu, add, sub_item.widget);
|
|
||||||
submenu.insert(sub_item.id, sub_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
$widget.set_submenu(Some(&menu));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let widget = match (info.menu_type, info.toggle_type) {
|
|
||||||
(MenuType::Separator, _) => {
|
|
||||||
TrayMenuWidget::Separator(SeparatorMenuItem::builder().visible(true).build())
|
|
||||||
}
|
|
||||||
(MenuType::Standard, ToggleType::Checkmark) => {
|
|
||||||
let widget = CheckMenuItem::builder()
|
|
||||||
.visible(info.visible)
|
|
||||||
.sensitive(info.enabled)
|
|
||||||
.active(info.toggle_state == ToggleState::On)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
setup_item(&widget, info);
|
|
||||||
add_submenu!(menu, widget);
|
|
||||||
|
|
||||||
{
|
|
||||||
let tx = tx.clone();
|
|
||||||
let id = info.id;
|
|
||||||
|
|
||||||
widget.connect_button_press_event(move |_item, _button| {
|
|
||||||
try_send!(tx, id);
|
|
||||||
Propagation::Proceed
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
TrayMenuWidget::Checkbox(widget)
|
|
||||||
}
|
|
||||||
(MenuType::Standard, _) => {
|
|
||||||
let widget = MenuItem::builder()
|
|
||||||
.visible(info.visible)
|
|
||||||
.sensitive(info.enabled)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
setup_item(&widget, info);
|
|
||||||
add_submenu!(menu, widget);
|
|
||||||
|
|
||||||
{
|
|
||||||
let tx = tx.clone();
|
|
||||||
let id = info.id;
|
|
||||||
|
|
||||||
widget.connect_button_press_event(move |_item, _event| {
|
|
||||||
try_send!(tx, id);
|
|
||||||
Propagation::Proceed
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
TrayMenuWidget::Standard(widget)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
id: info.id,
|
|
||||||
widget,
|
|
||||||
menu_widget: menu,
|
|
||||||
submenu,
|
|
||||||
tx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies a diff to this submenu item.
|
|
||||||
///
|
|
||||||
/// This is called recursively,
|
|
||||||
/// applying the submenu diffs to any further submenu items.
|
|
||||||
fn apply_diff(&mut self, diff: MenuItemDiff) {
|
|
||||||
if let Some(label) = diff.label {
|
|
||||||
let label = label.unwrap_or_default();
|
|
||||||
match &self.widget {
|
|
||||||
TrayMenuWidget::Separator(widget) => widget.set_label(&label),
|
|
||||||
TrayMenuWidget::Standard(widget) => widget.set_label(&label),
|
|
||||||
TrayMenuWidget::Checkbox(widget) => widget.set_label(&label),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Image support
|
|
||||||
if let Some(_icon_name) = diff.icon_name {
|
|
||||||
warn!("received unimplemented menu icon update");
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_icon_data) = diff.icon_data {
|
|
||||||
warn!("received unimplemented menu icon update");
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(enabled) = diff.enabled {
|
|
||||||
match &self.widget {
|
|
||||||
TrayMenuWidget::Separator(widget) => widget.set_sensitive(enabled),
|
|
||||||
TrayMenuWidget::Standard(widget) => widget.set_sensitive(enabled),
|
|
||||||
TrayMenuWidget::Checkbox(widget) => widget.set_sensitive(enabled),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(visible) = diff.visible {
|
|
||||||
match &self.widget {
|
|
||||||
TrayMenuWidget::Separator(widget) => widget.set_visible(visible),
|
|
||||||
TrayMenuWidget::Standard(widget) => widget.set_visible(visible),
|
|
||||||
TrayMenuWidget::Checkbox(widget) => widget.set_visible(visible),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(toggle_state) = diff.toggle_state {
|
|
||||||
if let TrayMenuWidget::Checkbox(widget) = &self.widget {
|
|
||||||
widget.set_active(toggle_state == ToggleState::On);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for sub_diff in diff.submenu {
|
|
||||||
match sub_diff {
|
|
||||||
Diff::Add(info) => {
|
|
||||||
let menu_item = TrayMenuItem::new(&info, self.tx.clone());
|
|
||||||
call!(self.menu_widget, add, menu_item.widget);
|
|
||||||
|
|
||||||
if let TrayMenuWidget::Standard(widget) = &self.widget {
|
|
||||||
widget.set_submenu(Some(&self.menu_widget));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.submenu.insert(menu_item.id, menu_item);
|
|
||||||
}
|
|
||||||
Diff::Update(id, diff) => {
|
|
||||||
if let Some(sub) = self.submenu.get_mut(&id) {
|
|
||||||
sub.apply_diff(diff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Diff::Remove(id) => {
|
|
||||||
if let Some(sub) = self.submenu.remove(&id) {
|
|
||||||
call!(self.menu_widget, remove, sub.widget);
|
|
||||||
}
|
|
||||||
if let TrayMenuWidget::Standard(widget) = &self.widget {
|
|
||||||
if self.submenu.is_empty() {
|
|
||||||
widget.set_submenu(None::<&Menu>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
mod diff;
|
|
||||||
mod icon;
|
mod icon;
|
||||||
mod interface;
|
mod interface;
|
||||||
|
|
||||||
use crate::clients::tray;
|
use crate::clients::tray;
|
||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::modules::tray::diff::get_diffs;
|
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||||
use crate::{glib_recv, lock, module_impl, send_async, spawn};
|
use crate::{glib_recv, lock, module_impl, send_async, spawn};
|
||||||
use color_eyre::{Report, Result};
|
use color_eyre::{Report, Result};
|
||||||
@ -124,7 +122,9 @@ impl Module<MenuBar> for TrayModule {
|
|||||||
// send tray commands
|
// send tray commands
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
while let Some(cmd) = rx.recv().await {
|
while let Some(cmd) = rx.recv().await {
|
||||||
client.activate(cmd).await?;
|
if let Err(err) = client.activate(cmd).await {
|
||||||
|
error!("{err:?}");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<_, Report>(())
|
Ok::<_, Report>(())
|
||||||
@ -158,7 +158,7 @@ impl Module<MenuBar> for TrayModule {
|
|||||||
|
|
||||||
// listen for UI updates
|
// listen for UI updates
|
||||||
glib_recv!(context.subscribe(), update =>
|
glib_recv!(context.subscribe(), update =>
|
||||||
on_update(update, &container, &mut menus, &icon_theme, self.icon_size, self.prefer_theme_icons, &context.controller_tx)
|
on_update(update, &container, &mut menus, &icon_theme, self.icon_size, self.prefer_theme_icons)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -178,13 +178,12 @@ fn on_update(
|
|||||||
icon_theme: &IconTheme,
|
icon_theme: &IconTheme,
|
||||||
icon_size: u32,
|
icon_size: u32,
|
||||||
prefer_icons: bool,
|
prefer_icons: bool,
|
||||||
tx: &mpsc::Sender<ActivateRequest>,
|
|
||||||
) {
|
) {
|
||||||
match update {
|
match update {
|
||||||
Event::Add(address, item) => {
|
Event::Add(address, item) => {
|
||||||
debug!("Received new tray item at '{address}': {item:?}");
|
debug!("Received new tray item at '{address}': {item:?}");
|
||||||
|
|
||||||
let mut menu_item = TrayMenu::new(tx.clone(), address.clone(), *item);
|
let mut menu_item = TrayMenu::new(*item);
|
||||||
container.add(&menu_item.widget);
|
container.add(&menu_item.widget);
|
||||||
|
|
||||||
if let Ok(image) = icon::get_image(&menu_item, icon_theme, icon_size, prefer_icons) {
|
if let Ok(image) = icon::get_image(&menu_item, icon_theme, icon_size, prefer_icons) {
|
||||||
@ -230,17 +229,14 @@ fn on_update(
|
|||||||
label_widget.set_label(&title.unwrap_or_default());
|
label_widget.set_label(&title.unwrap_or_default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// UpdateEvent::Tooltip(_tooltip) => {
|
UpdateEvent::Tooltip(tooltip) => {
|
||||||
// warn!("received unimplemented NewAttentionIcon event");
|
menu_item.set_tooltip(tooltip);
|
||||||
// }
|
|
||||||
UpdateEvent::Menu(menu) => {
|
|
||||||
debug!("received new menu for '{}'", address);
|
|
||||||
|
|
||||||
let diffs = get_diffs(menu_item.state(), &menu.submenus);
|
|
||||||
|
|
||||||
menu_item.apply_diffs(diffs);
|
|
||||||
menu_item.set_state(menu.submenus);
|
|
||||||
}
|
}
|
||||||
|
UpdateEvent::MenuConnect(menu) => {
|
||||||
|
let menu = system_tray::gtk_menu::Menu::new(&address, &menu);
|
||||||
|
menu_item.set_menu_widget(menu);
|
||||||
|
}
|
||||||
|
UpdateEvent::Menu(_) | UpdateEvent::MenuDiff(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Remove(address) => {
|
Event::Remove(address) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user