Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating status area icons #720

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions cosmic-applet-status-area/src/components/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,14 +244,10 @@ impl cosmic::Application for App {
fn view(&self) -> cosmic::Element<'_, Msg> {
let children = self.menus.iter().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)),
self.core
.applet
.icon_button_from_handle(menu.icon_handle().into())
.on_press_down(Msg::TogglePopup(*id)),
)
.on_enter(Msg::Hovered(*id))
.into()
Expand Down
36 changes: 30 additions & 6 deletions cosmic-applet-status-area/src/components/status_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@

use cosmic::{applet::menu_button, iced, widget::icon};

use crate::subscriptions::status_notifier_item::{Layout, StatusNotifierItem};
use crate::subscriptions::status_notifier_item::{IconNameOrPixmap, Layout, StatusNotifierItem};

#[derive(Clone, Debug)]
pub enum Msg {
Icon(Option<IconNameOrPixmap>),
Layout(Result<Layout, String>),
Tooltip(String),
Click(i32, bool),
}

#[derive(Debug)]
pub struct State {
item: StatusNotifierItem,
layout: Option<Layout>,
tooltip: String,
icon: Option<IconNameOrPixmap>,
expanded: Option<i32>,
}

Expand All @@ -24,13 +29,23 @@ impl State {
item,
layout: None,
expanded: None,
icon: None,
tooltip: Default::default(),
},
iced::Task::none(),
)
}

pub fn update(&mut self, message: Msg) -> iced::Task<Msg> {
match message {
Msg::Icon(icon) => {
self.icon = icon;
iced::Task::none()
}
Msg::Tooltip(tooltip) => {
self.tooltip = tooltip;
iced::Task::none()
}
Msg::Layout(layout) => {
match layout {
Ok(layout) => {
Expand Down Expand Up @@ -63,12 +78,15 @@ impl State {
self.item.name()
}

pub fn icon_name(&self) -> &str {
self.item.icon_name()
pub fn icon_handle(&self) -> icon::Handle {
self.icon
.as_ref()
.map(|i| i.clone().into())
.unwrap_or_else(|| icon::from_raster_bytes(&[]))
}

pub fn icon_pixmap(&self) -> Option<&icon::Handle> {
self.item.icon_pixmap()
pub fn tooltip(&self) -> &str {
&self.tooltip
}

pub fn popup_view(&self) -> cosmic::Element<Msg> {
Expand All @@ -80,7 +98,13 @@ impl State {
}

pub fn subscription(&self) -> iced::Subscription<Msg> {
self.item.layout_subscription().map(Msg::Layout)
let subs = vec![
self.item.icon_subscription().map(Msg::Icon),
self.item.tooltip_subscription().map(Msg::Tooltip),
self.item.layout_subscription().map(Msg::Layout),
];

iced::Subscription::batch(subs)
}

pub fn opened(&self) {
Expand Down
115 changes: 89 additions & 26 deletions cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ use zbus::zvariant::{self, OwnedValue};
#[derive(Clone, Debug)]
pub struct StatusNotifierItem {
name: String,
icon_name: String,
// TODO Handle icon with multiple sizes?
icon_pixmap: Option<icon::Handle>,
_item_proxy: StatusNotifierItemProxy<'static>,
// icon_name: String,
// icon_pixmap: Option<icon::Handle>,
item_proxy: StatusNotifierItemProxy<'static>,
menu_proxy: DBusMenuProxy<'static>,
}

Expand All @@ -25,6 +24,28 @@ pub struct Icon {
bytes: Vec<u8>,
}

#[derive(Clone, Debug)]
pub enum IconNameOrPixmap {
Name(String),
Pixmap(Icon),
}

impl From<IconNameOrPixmap> for icon::Handle {
fn from(value: IconNameOrPixmap) -> Self {
match value {
IconNameOrPixmap::Name(name) => icon::from_name(name).symbolic(true).into(),
IconNameOrPixmap::Pixmap(i) => {
let mut i = i.clone();
// 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).symbolic(true)
}
}
}
}

impl StatusNotifierItem {
pub async fn new(connection: &zbus::Connection, name: String) -> zbus::Result<Self> {
let (dest, path) = if let Some(idx) = name.find('/') {
Expand All @@ -39,21 +60,6 @@ impl StatusNotifierItem {
.build()
.await?;

let icon_name = item_proxy.icon_name().await.unwrap_or_default();
let icon_pixmap = item_proxy
.icon_pixmap()
.await
.unwrap_or_default()
.into_iter()
.max_by_key(|i| (i.width, i.height))
.map(|mut i| {
// 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 menu_path = item_proxy.menu().await?;
let menu_proxy = DBusMenuProxy::builder(connection)
.destination(dest.to_string())?
Expand All @@ -63,9 +69,7 @@ impl StatusNotifierItem {

Ok(Self {
name,
icon_name,
icon_pixmap,
_item_proxy: item_proxy,
item_proxy,
menu_proxy,
})
}
Expand All @@ -74,12 +78,35 @@ impl StatusNotifierItem {
&self.name
}

pub fn icon_name(&self) -> &str {
&self.icon_name
pub fn icon_subscription(&self) -> iced::Subscription<Option<IconNameOrPixmap>> {
let item_proxy = self.item_proxy.clone();
Subscription::run_with_id(
format!("status-notifier-icon-{}", &self.name),
async move {
let initial = futures::stream::once(get_icon(item_proxy.clone()));
let updates = item_proxy
.receive_new_icon()
.await
.unwrap()
.then(move |_| get_icon(item_proxy.clone()));
initial.chain(updates)
}
.flatten_stream(),
)
}

pub fn icon_pixmap(&self) -> Option<&icon::Handle> {
self.icon_pixmap.as_ref()
pub fn tooltip_subscription(&self) -> iced::Subscription<String> {
let item_proxy = self.item_proxy.clone();
Subscription::run_with_id(
format!("status-notifier-tooltip-{}", &self.name),
async move {
let initial = futures::stream::once(get_tooltip(item_proxy.clone()));
let update_stream = item_proxy.receive_new_tooltip().await.unwrap();
let updates = update_stream.then(move |_| get_tooltip(item_proxy.clone()));
initial.chain(updates)
}
.flatten_stream()
)
}

// TODO: Only fetch changed part of layout, if that's any faster
Expand Down Expand Up @@ -109,6 +136,27 @@ async fn get_layout(menu_proxy: DBusMenuProxy<'static>) -> Result<Layout, String
}
}

async fn get_tooltip(item_proxy: StatusNotifierItemProxy<'static>) -> String {
item_proxy.tooltip().await.unwrap_or_default()
}

async fn get_icon(item_proxy: StatusNotifierItemProxy<'static>) -> Option<IconNameOrPixmap> {
if let Ok(icon_name) = item_proxy.icon_name().await {
if icon_name != "" {
return Some(IconNameOrPixmap::Name(icon_name));
}
}

if let Ok(pixmaps) = item_proxy.icon_pixmap().await {
// TODO Handle icon with multiple sizes
return Some(IconNameOrPixmap::Pixmap(
pixmaps.into_iter().max_by_key(|i| (i.width, i.height))?,
));
}

None
}

#[zbus::proxy(interface = "org.kde.StatusNotifierItem")]
trait StatusNotifierItem {
#[zbus(property)]
Expand All @@ -118,8 +166,23 @@ trait StatusNotifierItem {
#[zbus(property)]
fn icon_pixmap(&self) -> zbus::Result<Vec<Icon>>;

#[zbus(property)]
fn title(&self) -> zbus::Result<String>;

#[zbus(property)]
fn tooltip(&self) -> zbus::Result<String>;

#[zbus(property)]
fn menu(&self) -> zbus::Result<zvariant::OwnedObjectPath>;

#[zbus(signal)]
fn new_title(&self) -> zbus::Result<()>;

#[zbus(signal)]
fn new_icon(&self) -> zbus::Result<()>;

#[zbus(signal)]
fn new_tooltip(&self) -> zbus::Result<()>;
}

#[derive(Clone, Debug)]
Expand Down