Merge branch 'master' into bulgarian
This commit is contained in:
commit
055befb30a
272 changed files with 4273 additions and 4972 deletions
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cosmic-applet-status-area"
|
||||
version = "0.1.0"
|
||||
version = "1.0.2"
|
||||
edition = "2024"
|
||||
license = "GPL-3.0-only"
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ Name[pt]=Área de Notificações
|
|||
Name[sk]=Panel oznámení
|
||||
Name[sv]=Fält för programikoner
|
||||
Name[es]=Bandeja de notificaciones
|
||||
Name[it]=Area notifiche
|
||||
Type=Application
|
||||
Exec=cosmic-applet-status-area
|
||||
Terminal=false
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use cosmic::{
|
|||
applet::token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription},
|
||||
cctk::sctk::reexports::calloop,
|
||||
iced::{
|
||||
self, Subscription,
|
||||
self, Length, Subscription,
|
||||
platform_specific::shell::commands::popup::{destroy_popup, get_popup},
|
||||
window,
|
||||
},
|
||||
|
|
@ -20,6 +20,8 @@ use crate::{components::status_menu, subscriptions::status_notifier_watcher};
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Msg {
|
||||
None,
|
||||
Activate(usize),
|
||||
Closed(window::Id),
|
||||
// XXX don't use index (unique window id? or I guess that's created and destroyed)
|
||||
StatusMenu((usize, status_menu::Msg)),
|
||||
|
|
@ -56,7 +58,7 @@ impl App {
|
|||
|
||||
fn resize_window(&self) -> app::Task<Msg> {
|
||||
let icon_size = self.core.applet.suggested_size(true).0 as u32
|
||||
+ self.core.applet.suggested_padding(true) as u32 * 2;
|
||||
+ self.core.applet.suggested_padding(true).1 as u32 * 2;
|
||||
let n = self.menus.len() as u32;
|
||||
window::resize(
|
||||
self.core.main_window_id().unwrap(),
|
||||
|
|
@ -73,8 +75,8 @@ impl App {
|
|||
}
|
||||
})?;
|
||||
|
||||
let button_total_size =
|
||||
self.core.applet.suggested_size(true).0 + self.core.applet.suggested_padding(true) * 2;
|
||||
let button_total_size = self.core.applet.suggested_size(true).0
|
||||
+ self.core.applet.suggested_padding(true).1 * 2;
|
||||
|
||||
let menu_count = self.menus.len();
|
||||
|
||||
|
|
@ -95,22 +97,11 @@ impl App {
|
|||
let overflow_index = self.overflow_index().unwrap_or(0);
|
||||
let children = self.menus.iter().skip(overflow_index).map(|(id, menu)| {
|
||||
mouse_area(
|
||||
match menu.icon_pixmap() {
|
||||
Some(icon) if menu.icon_name() == "" => self
|
||||
.core
|
||||
.applet
|
||||
.icon_button_from_handle(icon.clone().symbolic(true)),
|
||||
_ => self.core.applet.icon_button(menu.icon_name()),
|
||||
}
|
||||
.on_press_down(Msg::TogglePopup(*id)),
|
||||
menu_icon_button(&self.core.applet, &menu).on_press_down(Msg::TogglePopup(*id)),
|
||||
)
|
||||
.on_enter(Msg::Hovered(*id))
|
||||
.into()
|
||||
});
|
||||
let theme = self.core.system_theme();
|
||||
let cosmic = theme.cosmic();
|
||||
let corners = cosmic.corner_radii;
|
||||
let pad = corners.radius_m[0];
|
||||
|
||||
self.core
|
||||
.applet
|
||||
|
|
@ -158,6 +149,26 @@ impl cosmic::Application for App {
|
|||
|
||||
fn update(&mut self, message: Msg) -> app::Task<Msg> {
|
||||
match message {
|
||||
Msg::None => Task::none(),
|
||||
Msg::Activate(id) => {
|
||||
if let Some(token_tx) = self.token_tx.as_ref() {
|
||||
let _ = token_tx.send(TokenRequest {
|
||||
app_id: Self::APP_ID.to_string(),
|
||||
exec: format!("activate:{}", id),
|
||||
});
|
||||
} else {
|
||||
if let Some(menu) = self.menus.get(&id) {
|
||||
let item_proxy = menu.item.item_proxy().clone();
|
||||
return Task::future(async move {
|
||||
match item_proxy.activate(0, 0).await {
|
||||
Ok(_) => cosmic::action::app(Msg::None),
|
||||
Err(_) => cosmic::action::app(Msg::TogglePopup(id)),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Msg::Closed(surface) => {
|
||||
if self.popup == Some(surface) {
|
||||
self.popup = None;
|
||||
|
|
@ -237,7 +248,7 @@ impl cosmic::Application for App {
|
|||
Some((i, self.core.main_window_id().unwrap()))
|
||||
}
|
||||
})
|
||||
.unwrap_or((0, self.core.main_window_id().unwrap()));
|
||||
.unwrap_or((i, self.core.main_window_id().unwrap()));
|
||||
|
||||
let mut popup_settings = self
|
||||
.core
|
||||
|
|
@ -250,11 +261,11 @@ impl cosmic::Application for App {
|
|||
PanelAnchor::Left | PanelAnchor::Right
|
||||
) {
|
||||
let suggested_size = self.core.applet.suggested_size(false).1
|
||||
+ 2 * self.core.applet.suggested_padding(false);
|
||||
+ 2 * self.core.applet.suggested_padding(false).1;
|
||||
popup_settings.positioner.anchor_rect.y = i as i32 * suggested_size as i32;
|
||||
} else {
|
||||
let suggested_size = self.core.applet.suggested_size(false).0
|
||||
+ 2 * self.core.applet.suggested_padding(false);
|
||||
+ 2 * self.core.applet.suggested_padding(false).1;
|
||||
popup_settings.positioner.anchor_rect.x = i as i32 * suggested_size as i32;
|
||||
}
|
||||
cmds.push(get_popup(popup_settings));
|
||||
|
|
@ -276,6 +287,36 @@ impl cosmic::Application for App {
|
|||
return Task::none();
|
||||
}
|
||||
TokenUpdate::ActivationToken { token, exec: id } => {
|
||||
if let Some(id_str) = id.strip_prefix("activate:") {
|
||||
if let Ok(real_id) = id_str.parse::<usize>() {
|
||||
if let Some(menu) = self.menus.get(&real_id) {
|
||||
let item_proxy = menu.item.item_proxy().clone();
|
||||
let token = token.clone();
|
||||
let id = real_id;
|
||||
return Task::future(async move {
|
||||
if let Some(t) = token {
|
||||
match item_proxy.provide_xdg_activation_token(t).await {
|
||||
Ok(_) => {
|
||||
println!("Token provided successfully to {}", id)
|
||||
}
|
||||
Err(e) => eprintln!(
|
||||
"Failed to provide token to {}: {}",
|
||||
id, e
|
||||
),
|
||||
}
|
||||
}
|
||||
match item_proxy.activate(0, 0).await {
|
||||
Ok(_) => cosmic::action::app(Msg::None),
|
||||
Err(err) => {
|
||||
eprintln!("Activate failed: {}", err);
|
||||
cosmic::action::app(Msg::TogglePopup(id))
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
if let Some(((state, id), token)) = str::parse(&id)
|
||||
.ok()
|
||||
.and_then(|id: usize| self.menus.get_mut(&id).map(|m| (m, id)))
|
||||
|
|
@ -319,7 +360,7 @@ impl cosmic::Application for App {
|
|||
Some((i, self.core.main_window_id().unwrap()))
|
||||
}
|
||||
})
|
||||
.unwrap_or((0, self.core.main_window_id().unwrap()));
|
||||
.unwrap_or((i, self.core.main_window_id().unwrap()));
|
||||
|
||||
let mut popup_settings = self
|
||||
.core
|
||||
|
|
@ -331,12 +372,12 @@ impl cosmic::Application for App {
|
|||
self.core.applet.anchor,
|
||||
PanelAnchor::Left | PanelAnchor::Right
|
||||
) {
|
||||
let suggested_size = self.core.applet.suggested_size(false).1
|
||||
+ 2 * self.core.applet.suggested_padding(false);
|
||||
let suggested_size = self.core.applet.suggested_size(true).1
|
||||
+ 2 * self.core.applet.suggested_padding(true).1;
|
||||
popup_settings.positioner.anchor_rect.y = i as i32 * suggested_size as i32;
|
||||
} else {
|
||||
let suggested_size = self.core.applet.suggested_size(false).0
|
||||
+ 2 * self.core.applet.suggested_padding(false);
|
||||
let suggested_size = self.core.applet.suggested_size(true).0
|
||||
+ 2 * self.core.applet.suggested_padding(true).0;
|
||||
popup_settings.positioner.anchor_rect.x = i as i32 * suggested_size as i32;
|
||||
}
|
||||
cmds.push(get_popup(popup_settings));
|
||||
|
|
@ -368,13 +409,13 @@ impl cosmic::Application for App {
|
|||
self.core.applet.anchor,
|
||||
PanelAnchor::Left | PanelAnchor::Right
|
||||
) {
|
||||
let suggested_size = self.core.applet.suggested_size(false).1
|
||||
+ 2 * self.core.applet.suggested_padding(false);
|
||||
let suggested_size = self.core.applet.suggested_size(true).1
|
||||
+ 2 * self.core.applet.suggested_padding(true).1;
|
||||
popup_settings.positioner.anchor_rect.y =
|
||||
overflow_index as i32 * suggested_size as i32;
|
||||
} else {
|
||||
let suggested_size = self.core.applet.suggested_size(false).0
|
||||
+ 2 * self.core.applet.suggested_padding(false);
|
||||
let suggested_size = self.core.applet.suggested_size(true).0
|
||||
+ 2 * self.core.applet.suggested_padding(true).0;
|
||||
popup_settings.positioner.anchor_rect.x =
|
||||
overflow_index as i32 * suggested_size as i32;
|
||||
}
|
||||
|
|
@ -418,11 +459,11 @@ impl cosmic::Application for App {
|
|||
PanelAnchor::Left | PanelAnchor::Right
|
||||
) {
|
||||
let suggested_size = self.core.applet.suggested_size(false).1
|
||||
+ 2 * self.core.applet.suggested_padding(false);
|
||||
+ 2 * self.core.applet.suggested_padding(false).1;
|
||||
popup_settings.positioner.anchor_rect.y = i as i32 * suggested_size as i32;
|
||||
} else {
|
||||
let suggested_size = self.core.applet.suggested_size(false).0
|
||||
+ 2 * self.core.applet.suggested_padding(false);
|
||||
+ 2 * self.core.applet.suggested_padding(false).1;
|
||||
popup_settings.positioner.anchor_rect.x = i as i32 * suggested_size as i32;
|
||||
}
|
||||
cmds.push(get_popup(popup_settings));
|
||||
|
|
@ -452,18 +493,10 @@ impl cosmic::Application for App {
|
|||
.iter()
|
||||
.take(overflow_index.unwrap_or(self.menus.len()))
|
||||
.map(|(id, menu)| {
|
||||
mouse_area(
|
||||
match menu.icon_pixmap() {
|
||||
Some(icon) if menu.icon_name() == "" => self
|
||||
.core
|
||||
.applet
|
||||
.icon_button_from_handle(icon.clone().symbolic(true)),
|
||||
_ => self.core.applet.icon_button(menu.icon_name()),
|
||||
}
|
||||
.on_press_down(Msg::TogglePopup(*id)),
|
||||
)
|
||||
.on_enter(Msg::Hovered(*id))
|
||||
.into()
|
||||
mouse_area(menu_icon_button(&self.core.applet, &menu).on_press(Msg::Activate(*id)))
|
||||
.on_right_press(Msg::TogglePopup(*id))
|
||||
.on_enter(Msg::Hovered(*id))
|
||||
.into()
|
||||
});
|
||||
|
||||
self.core
|
||||
|
|
@ -530,6 +563,47 @@ impl cosmic::Application for App {
|
|||
}
|
||||
}
|
||||
|
||||
fn menu_icon_button<'a>(
|
||||
applet: &'a cosmic::applet::Context,
|
||||
menu: &'a status_menu::State,
|
||||
) -> cosmic::widget::Button<'a, Msg> {
|
||||
let icon = menu.icon_handle().clone();
|
||||
|
||||
let theme = cosmic::theme::active();
|
||||
let theme = theme.cosmic();
|
||||
|
||||
let suggested = applet.suggested_size(true);
|
||||
let padding = applet.suggested_padding(true).1;
|
||||
// let (major_padding, applet_padding_minor_axis) = applet.suggested_padding(true);
|
||||
// let (horizontal_padding, vertical_padding) = if applet.is_horizontal() {
|
||||
// (major_padding, applet_padding_minor_axis)
|
||||
// } else {
|
||||
// (applet_padding_minor_axis, major_padding)
|
||||
// };
|
||||
let symbolic = icon.symbolic;
|
||||
|
||||
cosmic::widget::button::custom(
|
||||
cosmic::widget::layer_container(
|
||||
cosmic::widget::icon(icon)
|
||||
.class(if symbolic {
|
||||
cosmic::theme::Svg::Custom(std::rc::Rc::new(|theme| {
|
||||
cosmic::iced_widget::svg::Style {
|
||||
color: Some(theme.cosmic().background.on.into()),
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
cosmic::theme::Svg::default()
|
||||
})
|
||||
.width(Length::Fixed(suggested.0 as f32))
|
||||
.height(Length::Fixed(suggested.1 as f32)),
|
||||
)
|
||||
.center(Length::Fill),
|
||||
)
|
||||
.width(Length::Fixed((suggested.0 + 2 * padding) as f32))
|
||||
.height(Length::Fixed((suggested.1 + 2 * padding) as f32))
|
||||
.class(cosmic::theme::Button::AppletIcon)
|
||||
}
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
cosmic::applet::run::<App>(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ use cosmic::{
|
|||
applet::{menu_button, token::subscription::TokenRequest},
|
||||
cctk::sctk::reexports::calloop,
|
||||
iced,
|
||||
widget::icon,
|
||||
widget::icon::{self, IconFallback},
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::subscriptions::status_notifier_item::{IconUpdate, Layout, StatusNotifierItem};
|
||||
|
||||
|
|
@ -20,12 +21,11 @@ pub enum Msg {
|
|||
}
|
||||
|
||||
pub struct State {
|
||||
item: StatusNotifierItem,
|
||||
pub item: StatusNotifierItem,
|
||||
layout: Option<Layout>,
|
||||
expanded: Option<i32>,
|
||||
icon_name: String,
|
||||
// TODO handle icon with multiple sizes?
|
||||
icon_pixmap: Option<icon::Handle>,
|
||||
icon_handle: icon::Handle,
|
||||
click_event: Option<(i32, bool)>,
|
||||
}
|
||||
|
||||
|
|
@ -36,8 +36,9 @@ impl State {
|
|||
item,
|
||||
layout: None,
|
||||
expanded: None,
|
||||
icon_name: String::new(),
|
||||
icon_pixmap: None,
|
||||
icon_handle: icon::from_name("application-default")
|
||||
.prefer_svg(true)
|
||||
.handle(),
|
||||
click_event: None,
|
||||
},
|
||||
iced::Task::none(),
|
||||
|
|
@ -61,30 +62,46 @@ impl State {
|
|||
iced::Task::none()
|
||||
}
|
||||
Msg::Icon(update) => {
|
||||
match update {
|
||||
IconUpdate::Name(name) => {
|
||||
self.icon_name = name;
|
||||
}
|
||||
IconUpdate::Pixmap(icons) => {
|
||||
self.icon_pixmap = icons
|
||||
.into_iter()
|
||||
.max_by_key(|i| (i.width, i.height))
|
||||
.map(|mut i| {
|
||||
if i.width <= 0 || i.height <= 0 || i.bytes.is_empty() {
|
||||
// App sent invalid icon data during initialization - show placeholder until NewIcon signal
|
||||
eprintln!("Skipping invalid icon: {}x{} with {} bytes, app may still be initializing",
|
||||
i.width, i.height, i.bytes.len());
|
||||
return icon::from_name("dialog-question").symbolic(true).handle();
|
||||
}
|
||||
// Convert ARGB to RGBA
|
||||
for pixel in i.bytes.chunks_exact_mut(4) {
|
||||
pixel.rotate_left(1);
|
||||
}
|
||||
icon::from_raster_pixels(i.width as u32, i.height as u32, i.bytes)
|
||||
});
|
||||
let icon_name = update.name.unwrap_or_default();
|
||||
|
||||
// Use the icon pixmap if an icon was not defined by name.
|
||||
if icon_name.is_empty() {
|
||||
let icon_pixmap = update.pixmap.and_then(|icons| icons
|
||||
.into_iter()
|
||||
.max_by_key(|i| (i.width, i.height))
|
||||
.map(|mut i| {
|
||||
if i.width <= 0 || i.height <= 0 || i.bytes.is_empty() {
|
||||
// App sent invalid icon data during initialization - show placeholder until NewIcon signal
|
||||
tracing::debug!("Skipping invalid icon: {}x{} with {} bytes, app may still be initializing",
|
||||
i.width, i.height, i.bytes.len());
|
||||
return icon::from_name("dialog-question").symbolic(true).handle();
|
||||
}
|
||||
// Convert ARGB to RGBA
|
||||
for pixel in i.bytes.chunks_exact_mut(4) {
|
||||
pixel.rotate_left(1);
|
||||
}
|
||||
icon::from_raster_pixels(i.width as u32, i.height as u32, i.bytes)
|
||||
}));
|
||||
|
||||
if let Some(icon) = icon_pixmap {
|
||||
self.icon_handle = icon.clone();
|
||||
return iced::Task::none();
|
||||
}
|
||||
}
|
||||
|
||||
// Load icon by path if the name is a path.
|
||||
self.icon_handle = if Path::new(&icon_name).exists() {
|
||||
icon::from_path(Path::new(&icon_name).to_path_buf()).symbolic(true)
|
||||
} else {
|
||||
icon::from_name(icon_name)
|
||||
.prefer_svg(true)
|
||||
.fallback(Some(IconFallback::Names(vec![
|
||||
"application-default".into(),
|
||||
"application-x-executable".into(),
|
||||
])))
|
||||
.handle()
|
||||
};
|
||||
|
||||
iced::Task::none()
|
||||
}
|
||||
Msg::Click(id, is_submenu) => {
|
||||
|
|
@ -101,9 +118,19 @@ impl State {
|
|||
let Some((id, is_submenu)) = self.click_event else {
|
||||
return iced::Task::none();
|
||||
};
|
||||
|
||||
let menu_proxy = self.item.menu_proxy().clone();
|
||||
let item_proxy = self.item.item_proxy().clone();
|
||||
|
||||
let Some(menu_proxy) = self.item.menu_proxy().cloned() else {
|
||||
tokio::spawn(async move {
|
||||
let _ = item_proxy.provide_xdg_activation_token(token).await;
|
||||
if let Err(err) = item_proxy.activate(0, 0).await {
|
||||
tracing::error!(
|
||||
"Error activating status notifier item without menu proxy: {err:?}"
|
||||
);
|
||||
}
|
||||
});
|
||||
return iced::Task::none();
|
||||
};
|
||||
tokio::spawn(async move {
|
||||
let _ = item_proxy.provide_xdg_activation_token(token).await;
|
||||
let _ = menu_proxy.event(id, "clicked", &0.into(), 0).await;
|
||||
|
|
@ -126,12 +153,8 @@ impl State {
|
|||
self.item.name()
|
||||
}
|
||||
|
||||
pub fn icon_name(&self) -> &str {
|
||||
&self.icon_name
|
||||
}
|
||||
|
||||
pub fn icon_pixmap(&self) -> Option<&icon::Handle> {
|
||||
self.icon_pixmap.as_ref()
|
||||
pub fn icon_handle(&self) -> &icon::Handle {
|
||||
&self.icon_handle
|
||||
}
|
||||
|
||||
pub fn popup_view(&self) -> cosmic::Element<'_, Msg> {
|
||||
|
|
@ -150,7 +173,9 @@ impl State {
|
|||
}
|
||||
|
||||
pub fn opened(&self) {
|
||||
let menu_proxy = self.item.menu_proxy().clone();
|
||||
let Some(menu_proxy) = self.item.menu_proxy().cloned() else {
|
||||
return;
|
||||
};
|
||||
tokio::spawn(async move {
|
||||
let _ = menu_proxy.event(0, "opened", &0i32.into(), 0).await;
|
||||
let _ = menu_proxy.about_to_show(0).await;
|
||||
|
|
@ -158,7 +183,9 @@ impl State {
|
|||
}
|
||||
|
||||
pub fn closed(&self) {
|
||||
let menu_proxy = self.item.menu_proxy().clone();
|
||||
let Some(menu_proxy) = self.item.menu_proxy().cloned() else {
|
||||
return;
|
||||
};
|
||||
tokio::spawn(async move {
|
||||
let _ = menu_proxy.event(0, "closed", &0i32.into(), 0).await;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@
|
|||
use cosmic::iced::{self, Subscription};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::path::PathBuf;
|
||||
use zbus::zvariant::{self, OwnedValue};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StatusNotifierItem {
|
||||
name: String,
|
||||
item_proxy: StatusNotifierItemProxy<'static>,
|
||||
menu_proxy: DBusMenuProxy<'static>,
|
||||
menu_proxy: Option<DBusMenuProxy<'static>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, zvariant::Value)]
|
||||
|
|
@ -21,9 +22,10 @@ pub struct Icon {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum IconUpdate {
|
||||
Name(String),
|
||||
Pixmap(Vec<Icon>),
|
||||
pub struct IconUpdate {
|
||||
pub name: Option<String>,
|
||||
pub pixmap: Option<Vec<Icon>>,
|
||||
// pub theme_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl StatusNotifierItem {
|
||||
|
|
@ -42,7 +44,22 @@ impl StatusNotifierItem {
|
|||
.build()
|
||||
.await?;
|
||||
|
||||
let menu_path = item_proxy.menu().await?;
|
||||
// XX: some items will not implement this but have a menu anyway
|
||||
let is_menu = item_proxy.item_is_menu().await;
|
||||
|
||||
let menu_path = item_proxy.menu().await;
|
||||
|
||||
// Why would an item say it has no menu but provide a menu path? Slack does this.
|
||||
let is_menu = menu_path.is_ok() || is_menu.unwrap_or(false);
|
||||
|
||||
if !is_menu {
|
||||
return Ok(Self {
|
||||
name,
|
||||
item_proxy,
|
||||
menu_proxy: None,
|
||||
});
|
||||
}
|
||||
let menu_path = menu_path?;
|
||||
let menu_proxy = DBusMenuProxy::builder(connection)
|
||||
.destination(dest.to_string())?
|
||||
.path(menu_path)?
|
||||
|
|
@ -52,7 +69,7 @@ impl StatusNotifierItem {
|
|||
Ok(Self {
|
||||
name,
|
||||
item_proxy,
|
||||
menu_proxy,
|
||||
menu_proxy: Some(menu_proxy),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -62,13 +79,22 @@ impl StatusNotifierItem {
|
|||
|
||||
// TODO: Only fetch changed part of layout, if that's any faster
|
||||
pub fn layout_subscription(&self) -> iced::Subscription<Result<Layout, String>> {
|
||||
let menu_proxy = self.menu_proxy.clone();
|
||||
let Some(menu_proxy) = self.menu_proxy.clone() else {
|
||||
return Subscription::none();
|
||||
};
|
||||
Subscription::run_with_id(
|
||||
format!("status-notifier-item-layout-{}", &self.name),
|
||||
async move {
|
||||
let initial = futures::stream::once(get_layout(menu_proxy.clone()));
|
||||
let layout_updated_stream = menu_proxy.receive_layout_updated().await.unwrap();
|
||||
let updates = layout_updated_stream.then(move |_| get_layout(menu_proxy.clone()));
|
||||
|
||||
let layout_updated = menu_proxy.receive_layout_updated().await.unwrap();
|
||||
let props_updated = menu_proxy.receive_items_properties_updated().await.unwrap();
|
||||
|
||||
// Merge both streams - any update triggers a layout refetch
|
||||
let updates =
|
||||
futures::stream_select!(layout_updated.map(|_| ()), props_updated.map(|_| ()))
|
||||
.then(move |()| get_layout(menu_proxy.clone()));
|
||||
|
||||
initial.chain(updates)
|
||||
}
|
||||
.flatten_stream(),
|
||||
|
|
@ -76,22 +102,15 @@ impl StatusNotifierItem {
|
|||
}
|
||||
|
||||
pub fn icon_subscription(&self) -> iced::Subscription<IconUpdate> {
|
||||
fn icon_events(
|
||||
item_proxy: StatusNotifierItemProxy<'static>,
|
||||
) -> impl futures::Stream<Item = IconUpdate> + 'static {
|
||||
async move {
|
||||
let icon_name = item_proxy.icon_name().await;
|
||||
let icon_pixmap = item_proxy.icon_pixmap().await;
|
||||
futures::stream::iter(
|
||||
[
|
||||
icon_name.map(IconUpdate::Name),
|
||||
icon_pixmap.map(IconUpdate::Pixmap),
|
||||
]
|
||||
.into_iter()
|
||||
.filter_map(Result::ok),
|
||||
)
|
||||
async fn icon_events(item_proxy: StatusNotifierItemProxy<'static>) -> IconUpdate {
|
||||
let icon_name = item_proxy.icon_name().await;
|
||||
let icon_pixmap = item_proxy.icon_pixmap().await;
|
||||
// let icon_theme_path = item_proxy.icon_theme_path().await.map(PathBuf::from);
|
||||
IconUpdate {
|
||||
name: icon_name.ok(),
|
||||
pixmap: icon_pixmap.ok(),
|
||||
// theme_path: icon_theme_path.ok().filter(|x| !x.as_os_str().is_empty()),
|
||||
}
|
||||
.flatten_stream()
|
||||
}
|
||||
|
||||
let item_proxy = self.item_proxy.clone();
|
||||
|
|
@ -101,14 +120,14 @@ impl StatusNotifierItem {
|
|||
let new_icon_stream = item_proxy.receive_new_icon().await.unwrap();
|
||||
futures::stream::once(async {})
|
||||
.chain(new_icon_stream.map(|_| ()))
|
||||
.flat_map(move |()| icon_events(item_proxy.clone()))
|
||||
.then(move |()| icon_events(item_proxy.clone()))
|
||||
}
|
||||
.flatten_stream(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn menu_proxy(&self) -> &DBusMenuProxy<'static> {
|
||||
&self.menu_proxy
|
||||
pub fn menu_proxy(&self) -> Option<&DBusMenuProxy<'static>> {
|
||||
self.menu_proxy.as_ref()
|
||||
}
|
||||
|
||||
pub fn item_proxy(&self) -> &StatusNotifierItemProxy<'static> {
|
||||
|
|
@ -128,6 +147,9 @@ pub trait StatusNotifierItem {
|
|||
#[zbus(property)]
|
||||
fn icon_name(&self) -> zbus::Result<String>;
|
||||
|
||||
#[zbus(property)]
|
||||
fn icon_theme_path(&self) -> zbus::Result<String>;
|
||||
|
||||
// https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/Icons
|
||||
#[zbus(property)]
|
||||
fn icon_pixmap(&self) -> zbus::Result<Vec<Icon>>;
|
||||
|
|
@ -135,10 +157,17 @@ pub trait StatusNotifierItem {
|
|||
#[zbus(property)]
|
||||
fn menu(&self) -> zbus::Result<zvariant::OwnedObjectPath>;
|
||||
|
||||
#[zbus(property)]
|
||||
fn item_is_menu(&self) -> zbus::Result<bool>;
|
||||
|
||||
#[zbus(signal)]
|
||||
fn new_icon(&self) -> zbus::Result<()>;
|
||||
|
||||
fn provide_xdg_activation_token(&self, token: String) -> zbus::Result<()>;
|
||||
|
||||
fn activate(&self, x: i32, y: i32) -> zbus::Result<()>;
|
||||
|
||||
fn secondary_activate(&self, x: i32, y: i32) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -261,4 +290,11 @@ pub trait DBusMenu {
|
|||
|
||||
#[zbus(signal)]
|
||||
fn layout_updated(&self, revision: u32, parent: i32) -> zbus::Result<()>;
|
||||
|
||||
#[zbus(signal)]
|
||||
fn items_properties_updated(
|
||||
&self,
|
||||
updated_props: Vec<(i32, std::collections::HashMap<String, zvariant::OwnedValue>)>,
|
||||
removed_props: Vec<(i32, Vec<String>)>,
|
||||
) -> zbus::Result<()>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue