fix: missing icons and actions from the dock

This commit is contained in:
Michael Aaron Murphy 2025-04-02 17:26:07 +02:00
parent 6b47c3b5b9
commit 25263dad7d
No known key found for this signature in database
GPG key ID: B2732D4240C9212C
10 changed files with 705 additions and 544 deletions

941
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -65,19 +65,20 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
tracing-log = "0.2.0"
tokio = { version = "1.43.0", features = ["full"] }
cosmic-config = { git = "https://github.com/pop-os/libcosmic" }
serde = { version = "1.0.217", features = ["derive"] }
freedesktop-desktop-entry = "0.7.8"
serde = { version = "1.0.219", features = ["derive"] }
[profile.release]
opt-level = "s"
panic = "abort"
lto = "fat"
[workspace.metadata.cargo-machete]
ignored = ["libcosmic"]
# [patch."https://github.com/pop-os/libcosmic"]
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "drop-menu-tree-changes" }
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "drop-menu-tree-changes" }
# iced_futures = { git = "https://github.com/pop-os/libcosmic//", branch = "drop-menu-tree-changes" }
# [patch."https://github.com/pop-os/libcosmic"]
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "desktop-entries-and-icons" }
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "desktop-entries-and-icons" }
# iced_futures = { git = "https://github.com/pop-os/libcosmic//", branch = "desktop-entries-and-icons" }
# cosmic-config = { path = "../libcosmic/cosmic-config" }
# libcosmic = { path = "../libcosmic" }
# iced_futures = { path = "../libcosmic/iced/futures" }

View file

@ -28,4 +28,3 @@ tracing-subscriber.workspace = true
tracing.workspace = true
url = "2.5.4"
zbus.workspace = true
freedesktop-desktop-entry.workspace = true

View file

@ -19,6 +19,8 @@ use cctk::{
workspace::v1::client::ext_workspace_handle_v1::ExtWorkspaceHandleV1,
},
};
use cosmic::desktop::fde;
use cosmic::desktop::fde::{get_languages_from_env, DesktopEntry};
use cosmic::{
app,
applet::{
@ -26,7 +28,7 @@ use cosmic::{
Context, Size,
},
cosmic_config::{Config, CosmicConfigEntry},
desktop::IconSource,
desktop::IconSourceExt,
iced::{
self,
clipboard::mime::{AllowedMimeTypes, AsMimeTypes},
@ -50,8 +52,6 @@ use cosmic::{
};
use cosmic_app_list_config::{AppListConfig, APP_ID};
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::State;
use freedesktop_desktop_entry as fde;
use freedesktop_desktop_entry::{get_languages_from_env, DesktopEntry};
use futures::future::pending;
use iced::{widget::container, Alignment, Background, Length};
use itertools::Itertools;
@ -159,7 +159,7 @@ impl DockItem {
let app_icon = AppletIconData::new(applet);
let cosmic_icon = IconSource::from_unknown(desktop_info.icon().unwrap_or_default())
let cosmic_icon = fde::IconSource::from_unknown(desktop_info.icon().unwrap_or_default())
.as_cosmic_icon()
.size(app_icon.icon_size);
@ -318,6 +318,7 @@ struct CosmicAppList {
popup: Option<Popup>,
subscription_ctr: u32,
item_ctr: u32,
desktop_entries: Vec<DesktopEntry>,
active_list: Vec<DockItem>,
pinned_list: Vec<DockItem>,
dnd_source: Option<(window::Id, DockItem, DndAction)>,
@ -562,28 +563,48 @@ fn app_list_icon_style(selected: bool) -> cosmic::theme::Button {
}
}
fn load_desktop_entries_from_app_ids<I, L>(ids: &[I], locales: &[L]) -> Vec<DesktopEntry>
where
I: AsRef<str>,
L: AsRef<str>,
{
let entries = fde::Iter::new(fde::default_paths())
.entries(Some(locales))
.collect::<Vec<_>>();
ids.iter()
.map(|id| {
fde::matching::find_entry_from_appid(entries.iter(), id.as_ref())
.unwrap_or(&fde::DesktopEntry::from_appid(id.as_ref().to_owned()))
.to_owned()
})
.collect_vec()
#[inline]
pub fn menu_control_padding() -> Padding {
let spacing = cosmic::theme::spacing();
[spacing.space_xxs, spacing.space_s].into()
}
pub fn menu_control_padding() -> Padding {
let theme = cosmic::theme::active();
let cosmic = theme.cosmic();
[cosmic.space_xxs(), cosmic.space_s()].into()
fn find_desktop_entries<'a>(
desktop_entries: &'a [fde::DesktopEntry],
app_ids: &'a [String],
) -> impl Iterator<Item = fde::DesktopEntry> + 'a {
app_ids.iter().map(|fav| {
let unicase_fav = fde::unicase::Ascii::new(fav.as_str());
fde::find_app_by_id(desktop_entries, unicase_fav)
.unwrap_or_else(|| {
panic!("could not find {fav}");
&fde::DesktopEntry::from_appid(fav.clone()).to_owned()
})
.to_owned()
})
}
impl CosmicAppList {
// Cache all desktop entries to use when new apps are added to the dock.
fn update_desktop_entries(&mut self) {
self.desktop_entries = fde::Iter::new(fde::default_paths())
.filter_map(|p| fde::DesktopEntry::from_path(p, Some(&self.locales)).ok())
.collect::<Vec<_>>();
}
// Update pinned items using the cached desktop entries as a source.
fn update_pinned_list(&mut self) {
self.pinned_list = find_desktop_entries(&self.desktop_entries, &self.config.favorites)
.zip(&self.config.favorites)
.enumerate()
.map(|(pinned_ctr, (e, original_id))| DockItem {
id: pinned_ctr as u32,
toplevels: Default::default(),
desktop_info: e.clone(),
original_app_id: original_id.clone(),
})
.collect();
}
}
impl cosmic::Application for CosmicAppList {
@ -598,25 +619,16 @@ impl cosmic::Application for CosmicAppList {
.and_then(|c| AppListConfig::get_entry(&c).ok())
.unwrap_or_default();
let locales = get_languages_from_env();
let mut app_list = Self {
core,
pinned_list: load_desktop_entries_from_app_ids(&config.favorites, &locales)
.into_iter()
.zip(&config.favorites)
.enumerate()
.map(|(pinned_ctr, (e, original_id))| DockItem {
id: pinned_ctr as u32,
toplevels: Default::default(),
desktop_info: e,
original_app_id: original_id.clone(),
})
.collect(),
config,
locales,
locales: get_languages_from_env(),
..Default::default()
};
app_list.update_desktop_entries();
app_list.update_pinned_list();
app_list.item_ctr = app_list.pinned_list.len() as u32;
(
@ -1051,9 +1063,29 @@ impl cosmic::Application for CosmicAppList {
}
WaylandUpdate::Toplevel(event) => match event {
ToplevelUpdate::Add(mut info) => {
let unicase_appid = fde::unicase::Ascii::new(&*info.app_id);
let new_desktop_info =
load_desktop_entries_from_app_ids(&[&info.app_id], &self.locales)
.remove(0);
match fde::find_app_by_id(&self.desktop_entries, unicase_appid) {
Some(appid) => appid,
None => {
// Update desktop entries in case it was not found.
self.update_desktop_entries();
match fde::find_app_by_id(
&self.desktop_entries,
unicase_appid,
) {
Some(appid) => appid,
None => {
tracing::error!(
id = info.app_id,
"could not find desktop entry for app"
);
return Task::none();
}
}
}
}
.clone();
if let Some(t) = self
.active_list
@ -1193,8 +1225,7 @@ impl cosmic::Application for CosmicAppList {
// pull back configured items into the favorites list
self.pinned_list =
load_desktop_entries_from_app_ids(&self.config.favorites, &self.locales)
.into_iter()
find_desktop_entries(&self.desktop_entries, &self.config.favorites)
.zip(&self.config.favorites)
.map(|(de, original_id)| {
if let Some(p) = self
@ -1212,7 +1243,7 @@ impl cosmic::Application for CosmicAppList {
DockItem {
id: self.item_ctr,
toplevels: Default::default(),
desktop_info: de,
desktop_info: de.clone(),
original_app_id: original_id.clone(),
}
}
@ -1419,7 +1450,7 @@ impl cosmic::Application for CosmicAppList {
),
dock_item
.desktop_info
.name(&self.locales)
.full_name(&self.locales)
.unwrap_or_default()
.to_string(),
self.popup.is_some(),
@ -1513,7 +1544,7 @@ impl cosmic::Application for CosmicAppList {
),
dock_item
.desktop_info
.name(&self.locales)
.full_name(&self.locales)
.unwrap_or_default()
.to_string(),
self.popup.is_some(),
@ -1662,7 +1693,7 @@ impl cosmic::Application for CosmicAppList {
let theme = self.core.system_theme();
if let Some((_, item, _)) = self.dnd_source.as_ref().filter(|s| s.0 == id) {
IconSource::from_unknown(item.desktop_info.icon().unwrap_or_default())
fde::IconSource::from_unknown(item.desktop_info.icon().unwrap_or_default())
.as_cosmic_icon()
.size(self.core.applet.suggested_size(false).0)
.into()
@ -1930,7 +1961,7 @@ impl cosmic::Application for CosmicAppList {
),
dock_item
.desktop_info
.name(&self.locales)
.full_name(&self.locales)
.unwrap_or_default()
.to_string(),
self.popup.is_some(),
@ -2029,7 +2060,7 @@ impl cosmic::Application for CosmicAppList {
),
dock_item
.desktop_info
.name(&self.locales)
.full_name(&self.locales)
.unwrap_or_default()
.to_string(),
self.popup.is_some(),

View file

@ -598,7 +598,7 @@ impl BluerSessionState {
)
.await;
res = adapter_clone.discover_devices_with_changes().await;
milli_timeout = (milli_timeout.saturating_mul(2));
milli_timeout = milli_timeout.saturating_mul(2);
}
milli_timeout = 10;
res.unwrap()
@ -612,7 +612,7 @@ impl BluerSessionState {
break 'outer;
}
} else {
milli_timeout = (milli_timeout.saturating_mul(2));
milli_timeout = milli_timeout.saturating_mul(2);
continue;
}
}

View file

@ -6,6 +6,8 @@ pub(crate) mod wayland_handler;
pub(crate) mod wayland_subscription;
pub(crate) mod window_image;
use std::borrow::Cow;
use crate::localize::localize;
use cosmic::{
app,
@ -14,7 +16,7 @@ use cosmic::{
sctk::reexports::calloop, toplevel_info::ToplevelInfo,
wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
},
desktop::DesktopEntryData,
desktop::fde,
iced::{
self,
id::Id as WidgetId,
@ -43,10 +45,20 @@ pub fn run() -> cosmic::iced::Result {
cosmic::applet::run::<Minimize>(())
}
pub struct App {
desktop_entry: fde::DesktopEntry,
name: String,
icon_source: fde::IconSource,
toplevel_info: ToplevelInfo,
wayland_image: Option<WaylandImage>,
}
#[derive(Default)]
struct Minimize {
core: cosmic::app::Core,
apps: Vec<(ToplevelInfo, DesktopEntryData, Option<WaylandImage>)>,
locales: Vec<String>,
desktop_entries: Vec<fde::DesktopEntry>,
apps: Vec<App>,
tx: Option<calloop::channel::Sender<WaylandRequest>>,
overflow_popup: Option<window::Id>,
}
@ -74,6 +86,34 @@ impl Minimize {
}
index
}
fn find_new_desktop_entry(&mut self, appid: &str) -> Option<fde::DesktopEntry> {
let unicase_appid = fde::unicase::Ascii::new(appid);
let de = match fde::find_app_by_id(&self.desktop_entries, unicase_appid) {
Some(de) => de,
None => {
// Update desktop entries in case it was not found.
self.update_desktop_entries();
match fde::find_app_by_id(&self.desktop_entries, unicase_appid) {
Some(appid) => appid,
None => {
tracing::error!(appid, "could not find desktop entry for app");
return None;
}
}
}
};
Some(de.clone())
}
// Cache all desktop entries to use when new apps are added to the dock.
fn update_desktop_entries(&mut self) {
self.desktop_entries = fde::Iter::new(fde::default_paths())
.filter_map(|p| fde::DesktopEntry::from_path(p, Some(&self.locales)).ok())
.collect::<Vec<_>>();
}
}
#[derive(Debug, Clone)]
@ -93,13 +133,15 @@ impl cosmic::Application for Minimize {
const APP_ID: &'static str = "com.system76.CosmicAppletMinimize";
fn init(core: cosmic::app::Core, _flags: ()) -> (Self, app::Task<Message>) {
(
Self {
core,
..Default::default()
},
Task::none(),
)
let mut app = Self {
core,
locales: fde::get_languages_from_env(),
..Default::default()
};
app.update_desktop_entries();
(app, Task::none())
}
fn core(&self) -> &cosmic::app::Core {
@ -124,32 +166,53 @@ impl cosmic::Application for Minimize {
panic!("Wayland Subscription ended...")
}
WaylandUpdate::Toplevel(t) => match t {
ToplevelUpdate::Add(info) | ToplevelUpdate::Update(info) => {
let data = |id| {
cosmic::desktop::load_applications_for_app_ids(
None,
std::iter::once(id),
true,
false,
)
.remove(0)
};
if let Some(pos) = self
.apps
.iter_mut()
.position(|a| a.0.foreign_toplevel == info.foreign_toplevel)
{
if self.apps[pos].0.app_id != info.app_id {
self.apps[pos].1 = data(&info.app_id)
ToplevelUpdate::Add(toplevel_info) | ToplevelUpdate::Update(toplevel_info) => {
// Temporarily take ownership to appease the borrow checker.
let mut apps = std::mem::take(&mut self.apps);
if let Some(pos) = apps.iter_mut().position(|a| {
a.toplevel_info.foreign_toplevel == toplevel_info.foreign_toplevel
}) {
if apps[pos].toplevel_info.app_id != toplevel_info.app_id {
apps[pos].desktop_entry =
match self.find_new_desktop_entry(&toplevel_info.app_id) {
Some(de) => de,
None => {
self.apps = apps;
return app::Task::none();
}
};
}
self.apps[pos].0 = info;
apps[pos].toplevel_info = toplevel_info;
} else {
let data = data(&info.app_id);
self.apps.push((info, data, None));
let desktop_entry =
match self.find_new_desktop_entry(&toplevel_info.app_id) {
Some(de) => de,
None => {
self.apps = apps;
return app::Task::none();
}
};
apps.push(App {
name: desktop_entry
.full_name(&self.locales)
.unwrap_or(Cow::Borrowed(&desktop_entry.appid))
.to_string(),
icon_source: fde::IconSource::from_unknown(
desktop_entry.icon().unwrap_or(&desktop_entry.appid),
),
desktop_entry,
toplevel_info,
wayland_image: None,
});
}
self.apps = apps;
}
ToplevelUpdate::Remove(handle) => {
self.apps.retain(|a| a.0.foreign_toplevel != handle);
self.apps
.retain(|a| a.toplevel_info.foreign_toplevel != handle);
self.apps.shrink_to_fit();
}
},
@ -157,9 +220,9 @@ impl cosmic::Application for Minimize {
if let Some(pos) = self
.apps
.iter()
.position(|a| a.0.foreign_toplevel == handle)
.position(|a| a.toplevel_info.foreign_toplevel == handle)
{
self.apps[pos].2 = Some(img);
self.apps[pos].wayland_image = Some(img);
}
}
},
@ -225,7 +288,7 @@ impl cosmic::Application for Minimize {
wayland_subscription::wayland_subscription().map(Message::Wayland)
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<Self::Message> {
let max_icon_count = self
.max_icon_count()
.map(|n| {
@ -235,23 +298,24 @@ impl cosmic::Application for Minimize {
self.apps.len()
}
})
.unwrap_or(self.apps.len());
.unwrap_or(self.apps.len())
.max(1);
let (width, _) = self.core.applet.suggested_size(false);
let padding = self.core.applet.suggested_padding(false);
let theme = self.core.system_theme().cosmic();
let space_xxs = theme.space_xxs();
let icon_buttons = self.apps[..max_icon_count].iter().map(|(info, data, img)| {
let icon_buttons = self.apps.iter().take(max_icon_count - 1).map(|app| {
self.core
.applet
.applet_tooltip(
Element::from(crate::window_image::WindowImage::new(
img.clone(),
&data.icon,
app.wayland_image.clone(),
&app.icon_source,
width as f32,
Message::Activate(info.foreign_toplevel.clone()),
Message::Activate(app.toplevel_info.foreign_toplevel.clone()),
padding,
)),
data.name.clone(),
app.name.clone(),
self.overflow_popup.is_some(),
Message::Surface,
)
@ -337,16 +401,16 @@ impl cosmic::Application for Minimize {
let padding = self.core.applet.suggested_padding(false);
let theme = self.core.system_theme().cosmic();
let space_xxs = theme.space_xxs();
let icon_buttons = self.apps[max_icon_count..].iter().map(|(info, data, img)| {
let icon_buttons = self.apps.iter().skip(max_icon_count).map(|app| {
tooltip(
Element::from(crate::window_image::WindowImage::new(
img.clone(),
&data.icon,
app.wayland_image.clone(),
&app.icon_source,
width as f32,
Message::Activate(info.foreign_toplevel.clone()),
Message::Activate(app.toplevel_info.foreign_toplevel.clone()),
padding,
)),
text(data.name.clone()).shaping(text::Shaping::Advanced),
text(&app.name).shaping(text::Shaping::Advanced),
// tooltip::Position::FollowCursor,
// FIXME tooltip fails to appear when created as indicated in design
// maybe it should be a subsurface

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::{
desktop::IconSource,
desktop::{fde, IconSourceExt},
iced::Limits,
iced_core::{layout, overlay, widget::Tree, Border, Layout, Length, Size, Vector},
theme::{Button, Container},
@ -23,7 +23,7 @@ where
{
pub fn new(
img: Option<WaylandImage>,
icon: &IconSource,
icon: &fde::IconSource,
size: f32,
on_press: Msg,
padding: u16,

View file

@ -5,7 +5,6 @@ edition = "2021"
license = "GPL-3.0-only"
[dependencies]
freedesktop-desktop-entry.workspace = true
libcosmic.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use config::{CosmicPanelButtonConfig, IndividualConfig, Override};
use cosmic::desktop::fde::{self, get_languages_from_env, DesktopEntry};
use cosmic::{
app,
applet::{
@ -15,7 +16,6 @@ use cosmic::{
Task,
};
use cosmic_config::{Config, CosmicConfigEntry};
use freedesktop_desktop_entry::{get_languages_from_env, DesktopEntry};
use once_cell::sync::Lazy;
use std::{env, fs, process::Command};
@ -177,7 +177,7 @@ pub fn run() -> iced::Result {
let mut desktop = None;
let locales = get_languages_from_env();
for mut path in freedesktop_desktop_entry::default_paths() {
for mut path in fde::default_paths() {
path.push(&filename);
if let Ok(bytes) = fs::read_to_string(&path) {
if let Ok(entry) = DesktopEntry::from_str(&path, &bytes, Some(&locales)) {

View file

@ -1,2 +1,2 @@
[toolchain]
channel = "1.80.1"
channel = "1.85.1"