mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-04-19 19:34:24 +02:00
refactor(tray): complete client rewrite
This commit is contained in:
parent
c7b6ee8bc0
commit
004ea76da5
9 changed files with 193 additions and 246 deletions
36
Cargo.lock
generated
36
Cargo.lock
generated
|
@ -101,12 +101,6 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anyhow"
|
|
||||||
version = "1.0.70"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -3117,18 +3111,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-tray"
|
name = "system-tray"
|
||||||
version = "0.1.5"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a456e3e6cbd396f1a3a91f8f74d1fdcf2bde85c97afe174442c367f4749fc09b"
|
checksum = "82a053bfb84b11f5eb8655a762ba826a2524d02a2f355b0fd6fce4125272f2e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
|
||||||
"byteorder",
|
|
||||||
"chrono",
|
|
||||||
"log",
|
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
@ -3157,18 +3146,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.56"
|
version = "1.0.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.56"
|
version = "1.0.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.35",
|
"quote 1.0.35",
|
||||||
|
@ -3243,7 +3232,6 @@ dependencies = [
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2 0.5.5",
|
"socket2 0.5.5",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"tracing",
|
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3268,17 +3256,6 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-stream"
|
|
||||||
version = "0.1.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"pin-project-lite",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.7"
|
version = "0.7.7"
|
||||||
|
@ -4144,7 +4121,6 @@ dependencies = [
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
"sha1",
|
"sha1",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"tokio",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"uds_windows",
|
"uds_windows",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
|
|
@ -131,7 +131,7 @@ mpris = { version = "2.0.1", optional = true }
|
||||||
sysinfo = { version = "0.29.11", optional = true }
|
sysinfo = { version = "0.29.11", optional = true }
|
||||||
|
|
||||||
# tray
|
# tray
|
||||||
system-tray = { version = "0.1.5", optional = true }
|
system-tray = { version = "0.2.0", optional = true }
|
||||||
|
|
||||||
# upower
|
# upower
|
||||||
upower_dbus = { version = "0.3.2", optional = true }
|
upower_dbus = { version = "0.3.2", optional = true }
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::Ironbar;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
|
@ -9,7 +10,7 @@ pub mod music;
|
||||||
#[cfg(feature = "notifications")]
|
#[cfg(feature = "notifications")]
|
||||||
pub mod swaync;
|
pub mod swaync;
|
||||||
#[cfg(feature = "tray")]
|
#[cfg(feature = "tray")]
|
||||||
pub mod system_tray;
|
pub mod tray;
|
||||||
#[cfg(feature = "upower")]
|
#[cfg(feature = "upower")]
|
||||||
pub mod upower;
|
pub mod upower;
|
||||||
#[cfg(feature = "volume")]
|
#[cfg(feature = "volume")]
|
||||||
|
@ -30,7 +31,7 @@ pub struct Clients {
|
||||||
#[cfg(feature = "notifications")]
|
#[cfg(feature = "notifications")]
|
||||||
notifications: Option<Arc<swaync::Client>>,
|
notifications: Option<Arc<swaync::Client>>,
|
||||||
#[cfg(feature = "tray")]
|
#[cfg(feature = "tray")]
|
||||||
tray: Option<Arc<system_tray::TrayEventReceiver>>,
|
tray: Option<Arc<tray::Client>>,
|
||||||
#[cfg(feature = "upower")]
|
#[cfg(feature = "upower")]
|
||||||
upower: Option<Arc<zbus::fdo::PropertiesProxy<'static>>>,
|
upower: Option<Arc<zbus::fdo::PropertiesProxy<'static>>>,
|
||||||
#[cfg(feature = "volume")]
|
#[cfg(feature = "volume")]
|
||||||
|
@ -85,11 +86,17 @@ impl Clients {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tray")]
|
#[cfg(feature = "tray")]
|
||||||
pub fn tray(&mut self) -> Arc<system_tray::TrayEventReceiver> {
|
pub fn tray(&mut self) -> Arc<tray::Client> {
|
||||||
|
// TODO: Error handling here isn't great - should throw a user-friendly error
|
||||||
self.tray
|
self.tray
|
||||||
.get_or_insert_with(|| {
|
.get_or_insert_with(|| {
|
||||||
Arc::new(crate::await_sync(async {
|
Arc::new(crate::await_sync(async {
|
||||||
system_tray::create_client().await
|
let service_name =
|
||||||
|
format!("{}-{}", env!("CARGO_CRATE_NAME"), Ironbar::unique_id());
|
||||||
|
|
||||||
|
tray::Client::new(&service_name)
|
||||||
|
.await
|
||||||
|
.expect("to be able to start client")
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.clone()
|
.clone()
|
||||||
|
|
|
@ -1,127 +0,0 @@
|
||||||
use crate::{arc_mut, lock, register_client, send, spawn, Ironbar};
|
|
||||||
use color_eyre::Report;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use system_tray::message::menu::TrayMenu;
|
|
||||||
use system_tray::message::tray::StatusNotifierItem;
|
|
||||||
use system_tray::message::{NotifierItemCommand, NotifierItemMessage};
|
|
||||||
use system_tray::StatusNotifierWatcher;
|
|
||||||
use tokio::sync::{broadcast, mpsc};
|
|
||||||
use tracing::{debug, error, trace};
|
|
||||||
|
|
||||||
type Tray = BTreeMap<String, (Box<StatusNotifierItem>, Option<TrayMenu>)>;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TrayEventReceiver {
|
|
||||||
tx: mpsc::Sender<NotifierItemCommand>,
|
|
||||||
b_tx: broadcast::Sender<NotifierItemMessage>,
|
|
||||||
_b_rx: broadcast::Receiver<NotifierItemMessage>,
|
|
||||||
|
|
||||||
tray: Arc<Mutex<Tray>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TrayEventReceiver {
|
|
||||||
async fn new() -> system_tray::error::Result<Self> {
|
|
||||||
let id = format!("ironbar-{}", Ironbar::unique_id());
|
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel(16);
|
|
||||||
let (b_tx, b_rx) = broadcast::channel(64);
|
|
||||||
|
|
||||||
let tray = StatusNotifierWatcher::new(rx).await?;
|
|
||||||
let mut host = Box::pin(tray.create_notifier_host(&id)).await?;
|
|
||||||
|
|
||||||
let tray = arc_mut!(BTreeMap::new());
|
|
||||||
|
|
||||||
{
|
|
||||||
let b_tx = b_tx.clone();
|
|
||||||
let tray = tray.clone();
|
|
||||||
|
|
||||||
spawn(async move {
|
|
||||||
while let Ok(message) = host.recv().await {
|
|
||||||
trace!("Received message: {message:?}");
|
|
||||||
|
|
||||||
send!(b_tx, message.clone());
|
|
||||||
let mut tray = lock!(tray);
|
|
||||||
match message {
|
|
||||||
NotifierItemMessage::Update {
|
|
||||||
address,
|
|
||||||
item,
|
|
||||||
menu,
|
|
||||||
} => {
|
|
||||||
debug!("Adding/updating item with address '{address}'");
|
|
||||||
tray.insert(address, (item, menu));
|
|
||||||
}
|
|
||||||
NotifierItemMessage::Remove { address } => {
|
|
||||||
debug!("Removing item with address '{address}'");
|
|
||||||
tray.remove(&address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok::<(), broadcast::error::SendError<NotifierItemMessage>>(())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
tx,
|
|
||||||
b_tx,
|
|
||||||
_b_rx: b_rx,
|
|
||||||
tray,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subscribe(
|
|
||||||
&self,
|
|
||||||
) -> (
|
|
||||||
mpsc::Sender<NotifierItemCommand>,
|
|
||||||
broadcast::Receiver<NotifierItemMessage>,
|
|
||||||
) {
|
|
||||||
let tx = self.tx.clone();
|
|
||||||
let b_rx = self.b_tx.subscribe();
|
|
||||||
|
|
||||||
let tray = lock!(self.tray).clone();
|
|
||||||
for (address, (item, menu)) in tray {
|
|
||||||
let update = NotifierItemMessage::Update {
|
|
||||||
address,
|
|
||||||
item,
|
|
||||||
menu,
|
|
||||||
};
|
|
||||||
send!(self.b_tx, update);
|
|
||||||
}
|
|
||||||
|
|
||||||
(tx, b_rx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempts to create a new `TrayEventReceiver` instance,
|
|
||||||
/// retrying a maximum of 10 times before panicking the thread.
|
|
||||||
pub async fn create_client() -> TrayEventReceiver {
|
|
||||||
const MAX_RETRIES: i32 = 10;
|
|
||||||
|
|
||||||
// sometimes this can fail
|
|
||||||
let mut retries = 0;
|
|
||||||
|
|
||||||
let value = loop {
|
|
||||||
retries += 1;
|
|
||||||
|
|
||||||
let tray = Box::pin(TrayEventReceiver::new()).await;
|
|
||||||
|
|
||||||
match tray {
|
|
||||||
Ok(tray) => break Some(tray),
|
|
||||||
Err(err) => error!(
|
|
||||||
"{:?}",
|
|
||||||
Report::new(err).wrap_err(format!(
|
|
||||||
"Failed to create StatusNotifierWatcher (attempt {retries})"
|
|
||||||
))
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
if retries == MAX_RETRIES {
|
|
||||||
break None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
value.expect("Failed to create StatusNotifierWatcher")
|
|
||||||
}
|
|
||||||
|
|
||||||
register_client!(TrayEventReceiver, tray);
|
|
4
src/clients/tray.rs
Normal file
4
src/clients/tray.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
use crate::register_client;
|
||||||
|
pub use system_tray::client::Client;
|
||||||
|
|
||||||
|
register_client!(Client, tray);
|
|
@ -1,9 +1,9 @@
|
||||||
use system_tray::message::menu::{MenuItem as MenuItemInfo, ToggleState};
|
use system_tray::menu::{MenuItem, ToggleState};
|
||||||
|
|
||||||
/// Diff change type and associated info.
|
/// Diff change type and associated info.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Diff {
|
pub enum Diff {
|
||||||
Add(MenuItemInfo),
|
Add(MenuItem),
|
||||||
Update(i32, MenuItemDiff),
|
Update(i32, MenuItemDiff),
|
||||||
Remove(i32),
|
Remove(i32),
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ pub enum Diff {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MenuItemDiff {
|
pub struct MenuItemDiff {
|
||||||
/// Text of the item,
|
/// Text of the item,
|
||||||
pub label: Option<String>,
|
pub label: Option<Option<String>>,
|
||||||
/// Whether the item can be activated or not.
|
/// Whether the item can be activated or not.
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
/// True if the item is visible in the menu.
|
/// True if the item is visible in the menu.
|
||||||
|
@ -29,7 +29,7 @@ pub struct MenuItemDiff {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuItemDiff {
|
impl MenuItemDiff {
|
||||||
fn new(old: &MenuItemInfo, new: &MenuItemInfo) -> Self {
|
fn new(old: &MenuItem, new: &MenuItem) -> Self {
|
||||||
macro_rules! diff {
|
macro_rules! diff {
|
||||||
($field:ident) => {
|
($field:ident) => {
|
||||||
if old.$field == new.$field {
|
if old.$field == new.$field {
|
||||||
|
@ -70,7 +70,7 @@ impl MenuItemDiff {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a diff set between old and new state.
|
/// Gets a diff set between old and new state.
|
||||||
pub fn get_diffs(old: &[MenuItemInfo], new: &[MenuItemInfo]) -> Vec<Diff> {
|
pub fn get_diffs(old: &[MenuItem], new: &[MenuItem]) -> Vec<Diff> {
|
||||||
let mut diffs = vec![];
|
let mut diffs = vec![];
|
||||||
|
|
||||||
for new_item in new {
|
for new_item in new {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::image::ImageProvider;
|
use crate::image::ImageProvider;
|
||||||
|
use crate::modules::tray::interface::TrayMenu;
|
||||||
use color_eyre::{Report, Result};
|
use color_eyre::{Report, Result};
|
||||||
use glib::ffi::g_strfreev;
|
use glib::ffi::g_strfreev;
|
||||||
use glib::translate::ToGlibPtr;
|
use glib::translate::ToGlibPtr;
|
||||||
|
@ -10,7 +11,6 @@ use std::collections::HashSet;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::os::raw::{c_char, c_int};
|
use std::os::raw::{c_char, c_int};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use system_tray::message::tray::StatusNotifierItem;
|
|
||||||
|
|
||||||
/// Gets the GTK icon theme search paths by calling the FFI function.
|
/// Gets the GTK icon theme search paths by calling the FFI function.
|
||||||
/// Conveniently returns the result as a `HashSet`.
|
/// Conveniently returns the result as a `HashSet`.
|
||||||
|
@ -38,17 +38,23 @@ fn get_icon_theme_search_paths(icon_theme: &IconTheme) -> HashSet<String> {
|
||||||
paths
|
paths
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_image(item: &StatusNotifierItem, icon_theme: &IconTheme, size: u32) -> Result<Image> {
|
pub fn get_image(
|
||||||
get_image_from_icon_name(item, icon_theme, size).or_else(|_| get_image_from_pixmap(item, size))
|
item: &TrayMenu,
|
||||||
|
icon_theme: &IconTheme,
|
||||||
|
size: u32,
|
||||||
|
prefer_icons: bool,
|
||||||
|
) -> Result<Image> {
|
||||||
|
if !prefer_icons && item.icon_pixmap.is_some() {
|
||||||
|
get_image_from_pixmap(item, size)
|
||||||
|
} else {
|
||||||
|
get_image_from_icon_name(item, icon_theme, size)
|
||||||
|
.or_else(|_| get_image_from_pixmap(item, size))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to get a GTK `Image` component
|
/// Attempts to get a GTK `Image` component
|
||||||
/// for the status notifier item's icon.
|
/// for the status notifier item's icon.
|
||||||
fn get_image_from_icon_name(
|
fn get_image_from_icon_name(item: &TrayMenu, icon_theme: &IconTheme, size: u32) -> Result<Image> {
|
||||||
item: &StatusNotifierItem,
|
|
||||||
icon_theme: &IconTheme,
|
|
||||||
size: u32,
|
|
||||||
) -> Result<Image> {
|
|
||||||
if let Some(path) = item.icon_theme_path.as_ref() {
|
if let Some(path) = item.icon_theme_path.as_ref() {
|
||||||
if !path.is_empty() && !get_icon_theme_search_paths(icon_theme).contains(path) {
|
if !path.is_empty() && !get_icon_theme_search_paths(icon_theme).contains(path) {
|
||||||
icon_theme.append_search_path(path);
|
icon_theme.append_search_path(path);
|
||||||
|
@ -59,18 +65,21 @@ fn get_image_from_icon_name(
|
||||||
icon_theme.lookup_icon(icon_name, size as i32, IconLookupFlags::empty())
|
icon_theme.lookup_icon(icon_name, size as i32, IconLookupFlags::empty())
|
||||||
});
|
});
|
||||||
|
|
||||||
let pixbuf = icon_info.unwrap().load_icon()?;
|
if let Some(icon_info) = icon_info {
|
||||||
|
let pixbuf = icon_info.load_icon()?;
|
||||||
let image = Image::new();
|
let image = Image::new();
|
||||||
ImageProvider::create_and_load_surface(&pixbuf, &image)?;
|
ImageProvider::create_and_load_surface(&pixbuf, &image)?;
|
||||||
Ok(image)
|
Ok(image)
|
||||||
|
} else {
|
||||||
|
Err(Report::msg("could not find icon"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to get an image from the item pixmap.
|
/// Attempts to get an image from the item pixmap.
|
||||||
///
|
///
|
||||||
/// The pixmap is supplied in ARGB32 format,
|
/// The pixmap is supplied in ARGB32 format,
|
||||||
/// which has 8 bits per sample and a bit stride of `4*width`.
|
/// which has 8 bits per sample and a bit stride of `4*width`.
|
||||||
fn get_image_from_pixmap(item: &StatusNotifierItem, size: u32) -> Result<Image> {
|
fn get_image_from_pixmap(item: &TrayMenu, size: u32) -> Result<Image> {
|
||||||
const BITS_PER_SAMPLE: i32 = 8;
|
const BITS_PER_SAMPLE: i32 = 8;
|
||||||
|
|
||||||
let pixmap = item
|
let pixmap = item
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::modules::tray::diff::{Diff, MenuItemDiff};
|
use super::diff::{Diff, MenuItemDiff};
|
||||||
use crate::{spawn, try_send};
|
use crate::{spawn, try_send};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{CheckMenuItem, Image, Label, Menu, MenuItem, SeparatorMenuItem};
|
use gtk::{CheckMenuItem, Image, Label, Menu, MenuItem, SeparatorMenuItem};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use system_tray::message::menu::{MenuItem as MenuItemInfo, MenuType, ToggleState, ToggleType};
|
use system_tray::client::ActivateRequest;
|
||||||
use system_tray::message::NotifierItemCommand;
|
use system_tray::item::{IconPixmap, StatusNotifierItem};
|
||||||
|
use system_tray::menu::{MenuItem as MenuItemInfo, MenuType, ToggleState, ToggleType};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
/// Calls a method on the underlying widget,
|
/// Calls a method on the underlying widget,
|
||||||
|
@ -49,37 +50,47 @@ macro_rules! call {
|
||||||
|
|
||||||
/// 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(crate) widget: MenuItem,
|
pub widget: MenuItem,
|
||||||
menu_widget: Menu,
|
menu_widget: Menu,
|
||||||
image_widget: Option<Image>,
|
image_widget: Option<Image>,
|
||||||
label_widget: Option<Label>,
|
label_widget: Option<Label>,
|
||||||
|
|
||||||
menu: HashMap<i32, TrayMenuItem>,
|
menu: HashMap<i32, TrayMenuItem>,
|
||||||
state: Vec<MenuItemInfo>,
|
state: Vec<MenuItemInfo>,
|
||||||
icon_name: Option<String>,
|
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub icon_name: Option<String>,
|
||||||
|
pub icon_theme_path: Option<String>,
|
||||||
|
pub icon_pixmap: Option<Vec<IconPixmap>>,
|
||||||
|
|
||||||
tx: mpsc::Sender<i32>,
|
tx: mpsc::Sender<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrayMenu {
|
impl TrayMenu {
|
||||||
pub fn new(tx: mpsc::Sender<NotifierItemCommand>, address: String, path: String) -> Self {
|
pub fn new(
|
||||||
|
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);
|
let (item_tx, mut item_rx) = mpsc::channel(8);
|
||||||
|
|
||||||
spawn(async move {
|
if let Some(menu) = item.menu {
|
||||||
while let Some(id) = item_rx.recv().await {
|
spawn(async move {
|
||||||
try_send!(
|
while let Some(id) = item_rx.recv().await {
|
||||||
tx,
|
try_send!(
|
||||||
NotifierItemCommand::MenuItemClicked {
|
tx,
|
||||||
submenu_id: id,
|
ActivateRequest {
|
||||||
menu_path: path.clone(),
|
submenu_id: id,
|
||||||
notifier_address: address.clone(),
|
menu_path: menu.clone(),
|
||||||
}
|
address: address.clone(),
|
||||||
);
|
}
|
||||||
}
|
);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let menu = Menu::new();
|
let menu = Menu::new();
|
||||||
widget.set_submenu(Some(&menu));
|
widget.set_submenu(Some(&menu));
|
||||||
|
@ -90,7 +101,10 @@ impl TrayMenu {
|
||||||
image_widget: None,
|
image_widget: None,
|
||||||
label_widget: None,
|
label_widget: None,
|
||||||
state: vec![],
|
state: vec![],
|
||||||
icon_name: None,
|
title: item.title,
|
||||||
|
icon_name: item.icon_name,
|
||||||
|
icon_theme_path: item.icon_theme_path,
|
||||||
|
icon_pixmap: item.icon_pixmap,
|
||||||
menu: HashMap::new(),
|
menu: HashMap::new(),
|
||||||
tx: item_tx,
|
tx: item_tx,
|
||||||
}
|
}
|
||||||
|
@ -112,6 +126,18 @@ impl TrayMenu {
|
||||||
.set_label(text);
|
.set_label(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shows the label, using its current text.
|
||||||
|
/// The image is hidden if present.
|
||||||
|
pub fn show_label(&self) {
|
||||||
|
if let Some(image) = &self.image_widget {
|
||||||
|
image.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(label) = &self.label_widget {
|
||||||
|
label.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Updates the image, and shows it in favour of the label.
|
/// Updates the image, and shows it in favour of the label.
|
||||||
pub fn set_image(&mut self, image: &Image) {
|
pub fn set_image(&mut self, image: &Image) {
|
||||||
if let Some(label) = &self.label_widget {
|
if let Some(label) = &self.label_widget {
|
||||||
|
@ -134,6 +160,7 @@ impl TrayMenu {
|
||||||
let item = TrayMenuItem::new(&info, self.tx.clone());
|
let item = TrayMenuItem::new(&info, self.tx.clone());
|
||||||
call!(self.menu_widget, add, item.widget);
|
call!(self.menu_widget, add, item.widget);
|
||||||
self.menu.insert(item.id, item);
|
self.menu.insert(item.id, item);
|
||||||
|
// self.widget.show_all();
|
||||||
}
|
}
|
||||||
Diff::Update(id, info) => {
|
Diff::Update(id, info) => {
|
||||||
if let Some(item) = self.menu.get_mut(&id) {
|
if let Some(item) = self.menu.get_mut(&id) {
|
||||||
|
@ -209,12 +236,15 @@ impl TrayMenuItem {
|
||||||
(MenuType::Separator, _) => TrayMenuWidget::Separator(SeparatorMenuItem::new()),
|
(MenuType::Separator, _) => TrayMenuWidget::Separator(SeparatorMenuItem::new()),
|
||||||
(MenuType::Standard, ToggleType::Checkmark) => {
|
(MenuType::Standard, ToggleType::Checkmark) => {
|
||||||
let widget = CheckMenuItem::builder()
|
let widget = CheckMenuItem::builder()
|
||||||
.label(info.label.as_str())
|
|
||||||
.visible(info.visible)
|
.visible(info.visible)
|
||||||
.sensitive(info.enabled)
|
.sensitive(info.enabled)
|
||||||
.active(info.toggle_state == ToggleState::On)
|
.active(info.toggle_state == ToggleState::On)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
if let Some(label) = &info.label {
|
||||||
|
widget.set_label(label);
|
||||||
|
}
|
||||||
|
|
||||||
add_submenu!(menu, widget);
|
add_submenu!(menu, widget);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -230,11 +260,14 @@ impl TrayMenuItem {
|
||||||
}
|
}
|
||||||
(MenuType::Standard, _) => {
|
(MenuType::Standard, _) => {
|
||||||
let widget = MenuItem::builder()
|
let widget = MenuItem::builder()
|
||||||
.label(&info.label)
|
|
||||||
.visible(info.visible)
|
.visible(info.visible)
|
||||||
.sensitive(info.enabled)
|
.sensitive(info.enabled)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
if let Some(label) = &info.label {
|
||||||
|
widget.set_label(label);
|
||||||
|
}
|
||||||
|
|
||||||
add_submenu!(menu, widget);
|
add_submenu!(menu, widget);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -265,6 +298,7 @@ impl TrayMenuItem {
|
||||||
/// applying the submenu diffs to any further submenu items.
|
/// applying the submenu diffs to any further submenu items.
|
||||||
fn apply_diff(&mut self, diff: MenuItemDiff) {
|
fn apply_diff(&mut self, diff: MenuItemDiff) {
|
||||||
if let Some(label) = diff.label {
|
if let Some(label) = diff.label {
|
||||||
|
let label = label.unwrap_or_default();
|
||||||
match &self.widget {
|
match &self.widget {
|
||||||
TrayMenuWidget::Separator(widget) => widget.set_label(&label),
|
TrayMenuWidget::Separator(widget) => widget.set_label(&label),
|
||||||
TrayMenuWidget::Standard(widget) => widget.set_label(&label),
|
TrayMenuWidget::Standard(widget) => widget.set_label(&label),
|
||||||
|
|
|
@ -2,19 +2,21 @@ mod diff;
|
||||||
mod icon;
|
mod icon;
|
||||||
mod interface;
|
mod interface;
|
||||||
|
|
||||||
use crate::clients::system_tray::TrayEventReceiver;
|
use crate::clients::tray;
|
||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::modules::tray::diff::get_diffs;
|
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, spawn};
|
use crate::{glib_recv, lock, send_async, spawn};
|
||||||
use color_eyre::Result;
|
use color_eyre::{Report, Result};
|
||||||
use gtk::{prelude::*, PackDirection};
|
use gtk::{prelude::*, PackDirection};
|
||||||
use gtk::{IconTheme, MenuBar};
|
use gtk::{IconTheme, MenuBar};
|
||||||
use interface::TrayMenu;
|
use interface::TrayMenu;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use system_tray::message::{NotifierItemCommand, NotifierItemMessage};
|
use system_tray::client::Event;
|
||||||
|
use system_tray::client::{ActivateRequest, UpdateEvent};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct TrayModule {
|
pub struct TrayModule {
|
||||||
|
@ -49,8 +51,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module<MenuBar> for TrayModule {
|
impl Module<MenuBar> for TrayModule {
|
||||||
type SendMessage = NotifierItemMessage;
|
type SendMessage = Event;
|
||||||
type ReceiveMessage = NotifierItemCommand;
|
type ReceiveMessage = ActivateRequest;
|
||||||
|
|
||||||
fn name() -> &'static str {
|
fn name() -> &'static str {
|
||||||
"tray"
|
"tray"
|
||||||
|
@ -64,26 +66,39 @@ impl Module<MenuBar> for TrayModule {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let tx = context.tx.clone();
|
let tx = context.tx.clone();
|
||||||
|
|
||||||
let client = context.client::<TrayEventReceiver>();
|
let client = context.client::<tray::Client>();
|
||||||
|
let mut tray_rx = client.subscribe();
|
||||||
|
|
||||||
let (tray_tx, mut tray_rx) = client.subscribe();
|
let initial_items = lock!(client.items()).clone();
|
||||||
|
|
||||||
// listen to tray updates
|
// listen to tray updates
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
while let Ok(message) = tray_rx.recv().await {
|
for (key, (item, menu)) in initial_items.into_iter() {
|
||||||
tx.send(ModuleUpdateEvent::Update(message)).await?;
|
send_async!(
|
||||||
|
tx,
|
||||||
|
ModuleUpdateEvent::Update(Event::Add(key.clone(), item.into()))
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(menu) = menu.clone() {
|
||||||
|
send_async!(
|
||||||
|
tx,
|
||||||
|
ModuleUpdateEvent::Update(Event::Update(key, UpdateEvent::Menu(menu)))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<(), mpsc::error::SendError<ModuleUpdateEvent<Self::SendMessage>>>(())
|
while let Ok(message) = tray_rx.recv().await {
|
||||||
|
send_async!(tx, ModuleUpdateEvent::Update(message))
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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 {
|
||||||
tray_tx.send(cmd).await?;
|
client.activate(cmd).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<(), mpsc::error::SendError<NotifierItemCommand>>(())
|
Ok::<_, Report>(())
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -114,7 +129,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, &context.controller_tx)
|
on_update(update, &container, &mut menus, &icon_theme, self.icon_size, self.prefer_theme_icons, &context.controller_tx)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -128,52 +143,81 @@ impl Module<MenuBar> for TrayModule {
|
||||||
/// Handles UI updates as callback,
|
/// Handles UI updates as callback,
|
||||||
/// getting the diff since the previous update and applying it to the menu.
|
/// getting the diff since the previous update and applying it to the menu.
|
||||||
fn on_update(
|
fn on_update(
|
||||||
update: NotifierItemMessage,
|
update: Event,
|
||||||
container: &MenuBar,
|
container: &MenuBar,
|
||||||
menus: &mut HashMap<Box<str>, TrayMenu>,
|
menus: &mut HashMap<Box<str>, TrayMenu>,
|
||||||
icon_theme: &IconTheme,
|
icon_theme: &IconTheme,
|
||||||
icon_size: u32,
|
icon_size: u32,
|
||||||
tx: &mpsc::Sender<NotifierItemCommand>,
|
prefer_icons: bool,
|
||||||
|
tx: &mpsc::Sender<ActivateRequest>,
|
||||||
) {
|
) {
|
||||||
match update {
|
match update {
|
||||||
NotifierItemMessage::Update {
|
Event::Add(address, item) => {
|
||||||
item,
|
debug!("Received new tray item at '{address}': {item:?}");
|
||||||
address,
|
|
||||||
menu,
|
|
||||||
} => {
|
|
||||||
if let (Some(menu_opts), Some(menu_path)) = (menu, &item.menu) {
|
|
||||||
let submenus = menu_opts.submenus;
|
|
||||||
|
|
||||||
let mut menu_item = menus.remove(address.as_str()).unwrap_or_else(|| {
|
let mut menu_item = TrayMenu::new(tx.clone(), address.clone(), *item);
|
||||||
let item = TrayMenu::new(tx.clone(), address.clone(), menu_path.to_string());
|
container.add(&menu_item.widget);
|
||||||
container.add(&item.widget);
|
|
||||||
|
|
||||||
item
|
match icon::get_image(&menu_item, icon_theme, icon_size, prefer_icons) {
|
||||||
});
|
Ok(image) => menu_item.set_image(&image),
|
||||||
|
Err(_) => {
|
||||||
let label = item.title.as_ref().unwrap_or(&address);
|
let label = menu_item.title.clone().unwrap_or(address.clone());
|
||||||
if let Some(label_widget) = menu_item.label_widget() {
|
menu_item.set_label(&label)
|
||||||
label_widget.set_label(label);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if item.icon_name.as_ref() != menu_item.icon_name() {
|
menu_item.widget.show();
|
||||||
match icon::get_image(&item, icon_theme, icon_size) {
|
menus.insert(address.into(), menu_item);
|
||||||
Ok(image) => menu_item.set_image(&image),
|
}
|
||||||
Err(_) => menu_item.set_label(label),
|
Event::Update(address, update) => {
|
||||||
};
|
debug!("Received tray update for '{address}': {update:?}");
|
||||||
|
|
||||||
|
let Some(menu_item) = menus.get_mut(address.as_str()) else {
|
||||||
|
error!("Attempted to update menu at '{address}' but could not find it");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match update {
|
||||||
|
UpdateEvent::AttentionIcon(_icon) => {
|
||||||
|
warn!("received unimplemented NewAttentionIcon event");
|
||||||
}
|
}
|
||||||
|
UpdateEvent::Icon(icon) => {
|
||||||
|
if icon.as_ref() != menu_item.icon_name() {
|
||||||
|
match icon::get_image(menu_item, icon_theme, icon_size, prefer_icons) {
|
||||||
|
Ok(image) => menu_item.set_image(&image),
|
||||||
|
Err(_) => menu_item.show_label(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let diffs = get_diffs(menu_item.state(), &submenus);
|
menu_item.set_icon_name(icon);
|
||||||
menu_item.apply_diffs(diffs);
|
}
|
||||||
menu_item.widget.show();
|
UpdateEvent::OverlayIcon(_icon) => {
|
||||||
|
warn!("received unimplemented NewOverlayIcon event");
|
||||||
|
}
|
||||||
|
UpdateEvent::Status(_status) => {
|
||||||
|
warn!("received unimplemented NewStatus event");
|
||||||
|
}
|
||||||
|
UpdateEvent::Title(title) => {
|
||||||
|
if let Some(label_widget) = menu_item.label_widget() {
|
||||||
|
label_widget.set_label(&title.unwrap_or_default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// UpdateEvent::Tooltip(_tooltip) => {
|
||||||
|
// warn!("received unimplemented NewAttentionIcon event");
|
||||||
|
// }
|
||||||
|
UpdateEvent::Menu(menu) => {
|
||||||
|
debug!("received new menu for '{}'", address);
|
||||||
|
|
||||||
menu_item.set_state(submenus);
|
let diffs = get_diffs(menu_item.state(), &menu.submenus);
|
||||||
menu_item.set_icon_name(item.icon_name);
|
|
||||||
|
|
||||||
menus.insert(address.into(), menu_item);
|
menu_item.apply_diffs(diffs);
|
||||||
|
menu_item.set_state(menu.submenus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NotifierItemMessage::Remove { address } => {
|
Event::Remove(address) => {
|
||||||
|
debug!("Removing tray item at '{address}'");
|
||||||
|
|
||||||
if let Some(menu) = menus.get(address.as_str()) {
|
if let Some(menu) = menus.get(address.as_str()) {
|
||||||
container.remove(&menu.widget);
|
container.remove(&menu.widget);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue