fix: update freedesktop-desktop-entry and fix app-list icons

This commit is contained in:
wiiznokes 2024-06-06 22:11:40 +02:00 committed by GitHub
parent c60a100135
commit 68fd2a6c17
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 229 additions and 208 deletions

60
Cargo.lock generated
View file

@ -904,6 +904,7 @@ dependencies = [
"anyhow", "anyhow",
"cosmic-client-toolkit", "cosmic-client-toolkit",
"cosmic-protocols", "cosmic-protocols",
"freedesktop-desktop-entry 0.6.0",
"futures", "futures",
"i18n-embed 0.14.1", "i18n-embed 0.14.1",
"i18n-embed-fl 0.8.0", "i18n-embed-fl 0.8.0",
@ -1205,7 +1206,7 @@ dependencies = [
[[package]] [[package]]
name = "cosmic-config" name = "cosmic-config"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"atomicwrites", "atomicwrites",
"cosmic-config-derive", "cosmic-config-derive",
@ -1227,7 +1228,7 @@ dependencies = [
[[package]] [[package]]
name = "cosmic-config-derive" name = "cosmic-config-derive"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"quote", "quote",
"syn 1.0.109", "syn 1.0.109",
@ -1274,7 +1275,7 @@ name = "cosmic-panel-button"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cosmic-config", "cosmic-config",
"freedesktop-desktop-entry", "freedesktop-desktop-entry 0.5.2",
"libcosmic", "libcosmic",
"serde", "serde",
"tracing", "tracing",
@ -1343,7 +1344,7 @@ dependencies = [
[[package]] [[package]]
name = "cosmic-theme" name = "cosmic-theme"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"almost", "almost",
"cosmic-config", "cosmic-config",
@ -2235,6 +2236,21 @@ dependencies = [
"xdg", "xdg",
] ]
[[package]]
name = "freedesktop-desktop-entry"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fefe79ec93a6aeaa938981fe3e11b4ed1b2f9deacc6bb631585bc48252d1bfa"
dependencies = [
"dirs 5.0.1",
"gettext-rs",
"memchr",
"strsim 0.11.1",
"textdistance",
"thiserror",
"xdg",
]
[[package]] [[package]]
name = "freedesktop-icons" name = "freedesktop-icons"
version = "0.2.6" version = "0.2.6"
@ -2767,7 +2783,7 @@ dependencies = [
[[package]] [[package]]
name = "iced" name = "iced"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"dnd", "dnd",
"iced_accessibility", "iced_accessibility",
@ -2785,7 +2801,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_accessibility" name = "iced_accessibility"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"accesskit", "accesskit",
"accesskit_unix", "accesskit_unix",
@ -2794,7 +2810,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_core" name = "iced_core"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"dnd", "dnd",
@ -2816,7 +2832,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_futures" name = "iced_futures"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"futures", "futures",
"iced_core", "iced_core",
@ -2829,7 +2845,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_graphics" name = "iced_graphics"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"bytemuck", "bytemuck",
@ -2853,7 +2869,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_renderer" name = "iced_renderer"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"iced_graphics", "iced_graphics",
"iced_tiny_skia", "iced_tiny_skia",
@ -2865,7 +2881,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_runtime" name = "iced_runtime"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"dnd", "dnd",
"iced_accessibility", "iced_accessibility",
@ -2879,7 +2895,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_sctk" name = "iced_sctk"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"enum-repr", "enum-repr",
"float-cmp", "float-cmp",
@ -2905,7 +2921,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_style" name = "iced_style"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"iced_core", "iced_core",
"once_cell", "once_cell",
@ -2915,7 +2931,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_tiny_skia" name = "iced_tiny_skia"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"cosmic-text", "cosmic-text",
@ -2932,7 +2948,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_wgpu" name = "iced_wgpu"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"bytemuck", "bytemuck",
@ -2958,7 +2974,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_widget" name = "iced_widget"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"dnd", "dnd",
"iced_renderer", "iced_renderer",
@ -3694,7 +3710,7 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]] [[package]]
name = "libcosmic" name = "libcosmic"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002"
dependencies = [ dependencies = [
"apply", "apply",
"ashpd", "ashpd",
@ -3707,7 +3723,7 @@ dependencies = [
"css-color", "css-color",
"derive_setters", "derive_setters",
"fraction", "fraction",
"freedesktop-desktop-entry", "freedesktop-desktop-entry 0.5.2",
"freedesktop-icons", "freedesktop-icons",
"iced", "iced",
"iced_core", "iced_core",
@ -3724,9 +3740,11 @@ dependencies = [
"palette", "palette",
"rfd", "rfd",
"ron", "ron",
"serde",
"shlex", "shlex",
"slotmap", "slotmap",
"taffy", "taffy",
"textdistance",
"thiserror", "thiserror",
"tokio", "tokio",
"tracing", "tracing",
@ -5628,6 +5646,12 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "textdistance"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d321c8576c2b47e43953e9cce236550d4cd6af0a6ce518fe084340082ca6037b"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.61" version = "1.0.61"

View file

@ -54,6 +54,7 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-log = "0.2.0" tracing-log = "0.2.0"
cosmic-config = { git = "https://github.com/pop-os/libcosmic" } cosmic-config = { git = "https://github.com/pop-os/libcosmic" }
serde = { version = "1.0.152", features = ["derive"] } serde = { version = "1.0.152", features = ["derive"] }
freedesktop-desktop-entry = "0.6.0"
[profile.release] [profile.release]
lto = "fat" lto = "fat"

View file

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

View file

@ -21,9 +21,7 @@ use cctk::wayland_client::protocol::wl_seat::WlSeat;
use cosmic::applet::cosmic_panel_config::PanelSize; use cosmic::applet::cosmic_panel_config::PanelSize;
use cosmic::applet::Size; use cosmic::applet::Size;
use cosmic::cosmic_config::{Config, CosmicConfigEntry}; use cosmic::cosmic_config::{Config, CosmicConfigEntry};
use cosmic::desktop::{ use cosmic::desktop::IconSource;
app_id_or_fallback_matches, load_applications_for_app_ids, DesktopEntryData,
};
use cosmic::iced; use cosmic::iced;
use cosmic::iced::event::listen_with; use cosmic::iced::event::listen_with;
use cosmic::iced::wayland::actions::data_device::DataFromMimeType; use cosmic::iced::wayland::actions::data_device::DataFromMimeType;
@ -34,7 +32,6 @@ use cosmic::iced::widget::dnd_listener;
use cosmic::iced::widget::vertical_rule; use cosmic::iced::widget::vertical_rule;
use cosmic::iced::widget::vertical_space; use cosmic::iced::widget::vertical_space;
use cosmic::iced::widget::{column, dnd_source, mouse_area, row, Column, Row}; use cosmic::iced::widget::{column, dnd_source, mouse_area, row, Column, Row};
use cosmic::iced::window::icon;
use cosmic::iced::Color; use cosmic::iced::Color;
use cosmic::iced::Vector; use cosmic::iced::Vector;
use cosmic::iced::{window, Subscription}; use cosmic::iced::{window, Subscription};
@ -70,6 +67,9 @@ use cosmic::{Element, Theme};
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::State; use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::State;
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1; use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1;
use cosmic_protocols::workspace::v1::client::zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1; use cosmic_protocols::workspace::v1::client::zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1;
use freedesktop_desktop_entry as fde;
use freedesktop_desktop_entry::get_languages_from_env;
use freedesktop_desktop_entry::DesktopEntry;
use futures::future::pending; use futures::future::pending;
use iced::widget::container; use iced::widget::container;
use iced::Alignment; use iced::Alignment;
@ -79,6 +79,7 @@ use itertools::Itertools;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use std::cmp::min; use std::cmp::min;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
@ -92,28 +93,6 @@ pub fn run() -> cosmic::iced::Result {
cosmic::applet::run::<CosmicAppList>(true, ()) cosmic::applet::run::<CosmicAppList>(true, ())
} }
pub fn load_applications_for_app_ids_sorted<'a, 'b>(
locale: impl Into<Option<&'a str>>,
app_ids: impl Iterator<Item = &'b str> + Clone,
fill_missing_ones: bool,
) -> Vec<DesktopEntryData> {
let mut ret = load_applications_for_app_ids(locale, app_ids.clone(), fill_missing_ones, false);
ret.sort_by(|a, b| {
app_ids
.clone()
.position(|id| id == a.id)
.unwrap_or(usize::MAX)
.cmp(
&app_ids
.clone()
.position(|id| id == b.id)
.unwrap_or(usize::MAX),
)
});
ret
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct AppletIconData { struct AppletIconData {
icon_size: u16, icon_size: u16,
@ -154,18 +133,26 @@ impl AppletIconData {
} }
} }
#[derive(Debug, Clone, Default)] type DockItemId = u32;
#[derive(Debug, Clone)]
struct DockItem { struct DockItem {
id: u32, // ID used internally in the applet. Each dock item
// have an unique id
id: DockItemId,
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo, Option<WaylandImage>)>, toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo, Option<WaylandImage>)>,
desktop_info: DesktopEntryData, // Information found in the .desktop file
desktop_info: DesktopEntry<'static>,
// We must use this because the id in `DesktopEntry` is an estimation.
// Thus, if we unpin an item, we want to be sure to use the real id
original_app_id: String,
} }
impl DataFromMimeType for DockItem { impl DataFromMimeType for DockItem {
fn from_mime_type(&self, mime_type: &str) -> Option<Vec<u8>> { fn from_mime_type(&self, mime_type: &str) -> Option<Vec<u8>> {
if mime_type == MIME_TYPE && self.desktop_info.path.is_some() { if mime_type == MIME_TYPE {
Some( Some(
Url::from_file_path(self.desktop_info.path.as_deref().unwrap()) Url::from_file_path(&self.desktop_info.path)
.ok()? .ok()?
.to_string() .to_string()
.as_bytes() .as_bytes()
@ -178,18 +165,6 @@ impl DataFromMimeType for DockItem {
} }
impl DockItem { impl DockItem {
fn new(
id: u32,
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo, Option<WaylandImage>)>,
desktop_info: DesktopEntryData,
) -> Self {
Self {
id,
toplevels,
desktop_info,
}
}
fn as_icon( fn as_icon(
&self, &self,
applet: &Context, applet: &Context,
@ -209,7 +184,9 @@ impl DockItem {
let app_icon = AppletIconData::new(applet); let app_icon = AppletIconData::new(applet);
let cosmic_icon = desktop_info.icon.as_cosmic_icon().size(app_icon.icon_size); let cosmic_icon = IconSource::from_unknown(desktop_info.icon().unwrap_or_default())
.as_cosmic_icon()
.size(app_icon.icon_size);
let dots = if toplevels.is_empty() { let dots = if toplevels.is_empty() {
(0..1) (0..1)
@ -288,15 +265,14 @@ impl DockItem {
} else if toplevels.len() == 1 { } else if toplevels.len() == 1 {
toplevels.first().map(|t| Message::Toggle(t.0.clone())) toplevels.first().map(|t| Message::Toggle(t.0.clone()))
} else { } else {
Some(Message::TopLevelListPopup(desktop_info.id.clone())) Some(Message::TopLevelListPopup(*id))
}) })
.width(Length::Shrink) .width(Length::Shrink)
.height(Length::Shrink), .height(Length::Shrink),
) )
.on_right_release(Message::Popup(desktop_info.id.clone())) .on_right_release(Message::Popup(*id))
.on_middle_release({ .on_middle_release({
launch_on_preferred_gpu(desktop_info, gpus) launch_on_preferred_gpu(desktop_info, gpus).unwrap_or_else(|| Message::Popup(*id))
.unwrap_or_else(|| Message::Popup(desktop_info.id.clone()))
}) })
.into() .into()
} else { } else {
@ -306,7 +282,7 @@ impl DockItem {
let icon_button = if dnd_source_enabled && interaction_enabled { let icon_button = if dnd_source_enabled && interaction_enabled {
dnd_source(icon_button) dnd_source(icon_button)
.drag_threshold(16.) .drag_threshold(16.)
.on_drag(|_, _| Message::StartDrag(desktop_info.id.clone())) .on_drag(|_, _| Message::StartDrag(*id))
.on_cancelled(Message::DragFinished) .on_cancelled(Message::DragFinished)
.on_finished(Message::DragFinished) .on_finished(Message::DragFinished)
} else { } else {
@ -330,7 +306,7 @@ struct DndOffer {
#[derive(Clone, Default)] #[derive(Clone, Default)]
struct CosmicAppList { struct CosmicAppList {
core: cosmic::app::Core, core: cosmic::app::Core,
popup: Option<(window::Id, u32, PopupType)>, popup: Option<(window::Id, DockItemId, PopupType)>,
subscription_ctr: u32, subscription_ctr: u32,
item_ctr: u32, item_ctr: u32,
active_list: Vec<DockItem>, active_list: Vec<DockItem>,
@ -339,13 +315,14 @@ struct CosmicAppList {
config: AppListConfig, config: AppListConfig,
wayland_sender: Option<Sender<WaylandRequest>>, wayland_sender: Option<Sender<WaylandRequest>>,
seat: Option<WlSeat>, seat: Option<WlSeat>,
rectangle_tracker: Option<RectangleTracker<u32>>, rectangle_tracker: Option<RectangleTracker<DockItemId>>,
rectangles: HashMap<u32, iced::Rectangle>, rectangles: HashMap<DockItemId, iced::Rectangle>,
dnd_offer: Option<DndOffer>, dnd_offer: Option<DndOffer>,
is_listening_for_dnd: bool, is_listening_for_dnd: bool,
gpus: Option<Vec<Gpu>>, gpus: Option<Vec<Gpu>>,
active_workspaces: Vec<ZcosmicWorkspaceHandleV1>, active_workspaces: Vec<ZcosmicWorkspaceHandleV1>,
output_list: HashMap<WlOutput, OutputInfo>, output_list: HashMap<WlOutput, OutputInfo>,
locales: Vec<String>,
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
@ -358,10 +335,10 @@ pub enum PopupType {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Message { enum Message {
Wayland(WaylandUpdate), Wayland(WaylandUpdate),
PinApp(String), PinApp(DockItemId),
UnpinApp(String), UnpinApp(DockItemId),
Popup(String), Popup(DockItemId),
TopLevelListPopup(String), TopLevelListPopup(DockItemId),
GpuRequest(Option<Vec<Gpu>>), GpuRequest(Option<Vec<Gpu>>),
CloseRequested(window::Id), CloseRequested(window::Id),
ClosePopup, ClosePopup,
@ -372,8 +349,8 @@ enum Message {
Ignore, Ignore,
NewSeat(WlSeat), NewSeat(WlSeat),
RemovedSeat(WlSeat), RemovedSeat(WlSeat),
Rectangle(RectangleUpdate<u32>), Rectangle(RectangleUpdate<DockItemId>),
StartDrag(String), // id of the DockItem StartDrag(DockItemId),
DragFinished, DragFinished,
DndEnter(f32, f32), DndEnter(f32, f32),
DndExit, DndExit,
@ -446,7 +423,7 @@ async fn try_get_gpus() -> Option<Vec<Gpu>> {
pub fn menu_button<'a, Message>( pub fn menu_button<'a, Message>(
content: impl Into<Element<'a, Message>>, content: impl Into<Element<'a, Message>>,
) -> cosmic::widget::Button<'a, Message> { ) -> cosmic::widget::Button<'a, Message> {
cosmic::widget::Button::new(content) cosmic::widget::button(content)
.style(Button::AppletMenu) .style(Button::AppletMenu)
.padding(menu_control_padding()) .padding(menu_control_padding())
.width(Length::Fill) .width(Length::Fill)
@ -466,7 +443,7 @@ where
Msg: 'static + Clone, Msg: 'static + Clone,
{ {
let border = 1.0; let border = 1.0;
cosmic::widget::Button::new( cosmic::widget::button(
container( container(
column![ column![
container(if let Some(img) = img { container(if let Some(img) = img {
@ -598,6 +575,33 @@ 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<'static>>
where
I: AsRef<str>,
L: AsRef<str>,
{
let srcs = fde::Iter::new(fde::default_paths())
.filter_map(|p| fs::read_to_string(&p).ok().and_then(|e| Some((p, e))))
.collect::<Vec<_>>();
let entries = srcs
.iter()
.filter_map(|(p, data)| DesktopEntry::from_str(p, data, locales).ok())
.collect::<Vec<_>>();
ids.iter()
.map(|id| {
fde::matching::get_best_match(
&[id],
&entries,
fde::matching::MatchAppIdOptions::default(),
)
.unwrap_or(&fde::DesktopEntry::from_appid(id.as_ref()))
.to_owned()
})
.collect_vec()
}
pub fn menu_control_padding() -> Padding { pub fn menu_control_padding() -> Padding {
let theme = cosmic::theme::active(); let theme = cosmic::theme::active();
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
@ -618,28 +622,30 @@ impl cosmic::Application for CosmicAppList {
.ok() .ok()
.and_then(|c| AppListConfig::get_entry(&c).ok()) .and_then(|c| AppListConfig::get_entry(&c).ok())
.unwrap_or_default(); .unwrap_or_default();
let mut self_ = Self {
let locales = get_languages_from_env();
let mut app_list = Self {
core, core,
pinned_list: load_applications_for_app_ids_sorted( pinned_list: load_desktop_entries_from_app_ids(&config.favorites, &locales)
None, .into_iter()
config.favorites.iter().map(|s| &**s), .zip(&config.favorites)
true, .enumerate()
) .map(|(pinned_ctr, (e, original_id))| DockItem {
.into_iter() id: pinned_ctr as u32,
.enumerate() toplevels: Default::default(),
.map(|(pinned_ctr, e)| DockItem { desktop_info: e,
id: pinned_ctr as u32, original_app_id: original_id.clone(),
toplevels: Default::default(), })
desktop_info: e, .collect(),
})
.collect(),
config, config,
locales,
..Default::default() ..Default::default()
}; };
self_.item_ctr = self_.pinned_list.len() as u32; app_list.item_ctr = app_list.pinned_list.len() as u32;
( (
self_, app_list,
Command::perform(try_get_gpus(), |gpus| { Command::perform(try_get_gpus(), |gpus| {
cosmic::app::Message::App(Message::GpuRequest(gpus)) cosmic::app::Message::App(Message::GpuRequest(gpus))
}), }),
@ -667,7 +673,7 @@ impl cosmic::Application for CosmicAppList {
.active_list .active_list
.iter() .iter()
.chain(self.pinned_list.iter()) .chain(self.pinned_list.iter())
.find(|t| t.desktop_info.id == id) .find(|t| t.id == id)
{ {
let rectangle = match self.rectangles.get(&toplevel_group.id) { let rectangle = match self.rectangles.get(&toplevel_group.id) {
Some(r) => r, Some(r) => r,
@ -711,7 +717,7 @@ impl cosmic::Application for CosmicAppList {
.active_list .active_list
.iter() .iter()
.chain(self.pinned_list.iter()) .chain(self.pinned_list.iter())
.find(|t| t.desktop_info.id == id) .find(|t| t.id == id)
{ {
for (ref handle, _, _) in &toplevel_group.toplevels { for (ref handle, _, _) in &toplevel_group.toplevels {
if let Some(tx) = self.wayland_sender.as_ref() { if let Some(tx) = self.wayland_sender.as_ref() {
@ -770,33 +776,29 @@ impl cosmic::Application for CosmicAppList {
return get_popup(popup_settings); return get_popup(popup_settings);
} }
} }
Message::PinApp(id) => { Message::PinApp(id) => {
if let Some(i) = self if let Some(i) = self.active_list.iter().position(|t| t.id == id) {
.active_list
.iter()
.position(|t| t.desktop_info.id == id)
{
let entry = self.active_list.remove(i); let entry = self.active_list.remove(i);
self.config.add_pinned(
entry.original_app_id.clone(),
&Config::new(APP_ID, AppListConfig::VERSION).unwrap(),
);
self.pinned_list.push(entry); self.pinned_list.push(entry);
} }
self.config
.add_pinned(id, &Config::new(APP_ID, AppListConfig::VERSION).unwrap());
if let Some((popup_id, _toplevel, _)) = self.popup.take() { if let Some((popup_id, _toplevel, _)) = self.popup.take() {
return destroy_popup(popup_id); return destroy_popup(popup_id);
} }
} }
Message::UnpinApp(id) => { Message::UnpinApp(id) => {
self.config.remove_pinned( if let Some(i) = self.pinned_list.iter().position(|t| t.id == id) {
id.clone(),
&Config::new(APP_ID, AppListConfig::VERSION).unwrap(),
);
if let Some(i) = self
.pinned_list
.iter()
.position(|t| t.desktop_info.id == id)
{
let entry = self.pinned_list.remove(i); let entry = self.pinned_list.remove(i);
self.config.remove_pinned(
&entry.original_app_id,
&Config::new(APP_ID, AppListConfig::VERSION).unwrap(),
);
self.rectangles.remove(&entry.id); self.rectangles.remove(&entry.id);
if !entry.toplevels.is_empty() { if !entry.toplevels.is_empty() {
self.active_list.push(entry); self.active_list.push(entry);
@ -833,7 +835,7 @@ impl cosmic::Application for CosmicAppList {
.active_list .active_list
.iter() .iter()
.chain(self.pinned_list.iter()) .chain(self.pinned_list.iter())
.find(|t| t.desktop_info.id == id) .find(|t| t.desktop_info.id() == id)
{ {
for (handle, _, _) in &toplevel_group.toplevels { for (handle, _, _) in &toplevel_group.toplevels {
if let Some(tx) = self.wayland_sender.as_ref() { if let Some(tx) = self.wayland_sender.as_ref() {
@ -852,21 +854,17 @@ impl cosmic::Application for CosmicAppList {
.active_list .active_list
.iter() .iter()
.find_map(|t| { .find_map(|t| {
if t.desktop_info.id == id { if t.id == id {
Some((false, t.clone())) Some((false, t.clone()))
} else { } else {
None None
} }
}) })
.or_else(|| { .or_else(|| {
if let Some(pos) = self if let Some(pos) = self.pinned_list.iter().position(|t| t.id == id) {
.pinned_list
.iter()
.position(|t| t.desktop_info.id == id)
{
let t = self.pinned_list.remove(pos); let t = self.pinned_list.remove(pos);
self.config.remove_pinned( self.config.remove_pinned(
t.desktop_info.id.clone(), &t.original_app_id,
&Config::new(APP_ID, AppListConfig::VERSION).unwrap(), &Config::new(APP_ID, AppListConfig::VERSION).unwrap(),
); );
Some((true, t)) Some((true, t))
@ -896,7 +894,7 @@ impl cosmic::Application for CosmicAppList {
.pinned_list .pinned_list
.iter() .iter()
.chain(self.active_list.iter()) .chain(self.active_list.iter())
.any(|t| t.desktop_info.id == toplevel_group.desktop_info.id) .any(|t| t.desktop_info.id() == toplevel_group.desktop_info.id())
&& !toplevel_group.toplevels.is_empty() && !toplevel_group.toplevels.is_empty()
{ {
self.item_ctr += 1; self.item_ctr += 1;
@ -959,9 +957,14 @@ impl cosmic::Application for CosmicAppList {
} }
Message::DndData(file_path) => { Message::DndData(file_path) => {
if let Some(DndOffer { dock_item, .. }) = self.dnd_offer.as_mut() { if let Some(DndOffer { dock_item, .. }) = self.dnd_offer.as_mut() {
if let Some(di) = cosmic::desktop::load_desktop_file(None, file_path) { if let Ok(de) = fde::DesktopEntry::from_path(file_path, &self.locales) {
self.item_ctr += 1; self.item_ctr += 1;
*dock_item = Some(DockItem::new(self.item_ctr, Vec::new(), di)); *dock_item = Some(DockItem {
id: self.item_ctr,
toplevels: Vec::new(),
original_app_id: de.id().to_string(),
desktop_info: de,
});
} }
} }
} }
@ -978,16 +981,12 @@ impl cosmic::Application for CosmicAppList {
if let Some((pos, is_pinned)) = self if let Some((pos, is_pinned)) = self
.active_list .active_list
.iter() .iter()
.position(|DockItem { desktop_info, .. }| { .position(|de| de.original_app_id == dock_item.original_app_id)
desktop_info.id == dock_item.desktop_info.id
})
.map(|pos| (pos, false)) .map(|pos| (pos, false))
.or_else(|| { .or_else(|| {
self.pinned_list self.pinned_list
.iter() .iter()
.position(|DockItem { desktop_info, .. }| { .position(|de| de.original_app_id == dock_item.original_app_id)
desktop_info.id == dock_item.desktop_info.id
})
.map(|pos| (pos, true)) .map(|pos| (pos, true))
}) })
{ {
@ -1000,13 +999,13 @@ impl cosmic::Application for CosmicAppList {
}; };
dock_item.id = self.item_ctr; dock_item.id = self.item_ctr;
if dock_item.desktop_info.exec.is_some() { if dock_item.desktop_info.exec().is_some() {
self.pinned_list self.pinned_list
.insert(index.min(self.pinned_list.len()), dock_item); .insert(index.min(self.pinned_list.len()), dock_item);
self.config.update_pinned( self.config.update_pinned(
self.pinned_list self.pinned_list
.iter() .iter()
.map(|dock_item| dock_item.desktop_info.id.clone()) .map(|dock_item| dock_item.original_app_id.clone())
.collect(), .collect(),
&Config::new(APP_ID, AppListConfig::VERSION).unwrap(), &Config::new(APP_ID, AppListConfig::VERSION).unwrap(),
); );
@ -1060,12 +1059,16 @@ impl cosmic::Application for CosmicAppList {
} }
WaylandUpdate::Toplevel(event) => match event { WaylandUpdate::Toplevel(event) => match event {
ToplevelUpdate::Add(handle, mut info) => { ToplevelUpdate::Add(handle, mut info) => {
let new_desktop_info =
load_desktop_entries_from_app_ids(&[&info.app_id], &self.locales)
.remove(0);
if let Some(t) = self if let Some(t) = self
.active_list .active_list
.iter_mut() .iter_mut()
.chain(self.pinned_list.iter_mut()) .chain(self.pinned_list.iter_mut())
.find(|DockItem { desktop_info, .. }| { .find(|DockItem { desktop_info, .. }| {
app_id_or_fallback_matches(&info.app_id, desktop_info) desktop_info.id() == new_desktop_info.id()
}) })
{ {
t.toplevels.push((handle, info, None)); t.toplevels.push((handle, info, None));
@ -1074,18 +1077,12 @@ impl cosmic::Application for CosmicAppList {
info.app_id = format!("Unknown Application {}", self.item_ctr); info.app_id = format!("Unknown Application {}", self.item_ctr);
} }
self.item_ctr += 1; self.item_ctr += 1;
let desktop_info = load_applications_for_app_ids_sorted(
None,
std::iter::once(&*info.app_id),
true,
)
.into_iter()
.next()
.unwrap();
self.active_list.push(DockItem { self.active_list.push(DockItem {
id: self.item_ctr, id: self.item_ctr,
original_app_id: info.app_id.clone(),
toplevels: vec![(handle, info, None)], toplevels: vec![(handle, info, None)],
desktop_info, desktop_info: new_desktop_info,
}); });
} }
} }
@ -1196,33 +1193,38 @@ impl cosmic::Application for CosmicAppList {
self.config = config; self.config = config;
// drain to active list // drain to active list
for item in self.pinned_list.drain(..) { for item in self.pinned_list.drain(..) {
self.active_list.push(item); if !item.toplevels.is_empty() {
self.active_list.push(item);
}
} }
// pull back configured items into the pinned app list // pull back configured items into the favorites list
self.pinned_list = load_applications_for_app_ids_sorted( self.pinned_list =
None, load_desktop_entries_from_app_ids(&self.config.favorites, &self.locales)
self.config.favorites.iter().map(|s| &**s), .into_iter()
true, .zip(&self.config.favorites)
) .map(|(de, original_id)| {
.into_iter() if let Some(p) = self
.map(|new_dock_item| { .active_list
if let Some(p) = self .iter()
.active_list // match using heuristic id
.iter() .position(|dock_item| dock_item.desktop_info.id() == de.id())
.position(|dock_item| dock_item.desktop_info.id == new_dock_item.id) {
{ let mut d = self.active_list.remove(p);
self.active_list.remove(p) // but use the id from the config
} else { d.original_app_id = original_id.clone();
self.item_ctr += 1; d
DockItem { } else {
id: self.item_ctr, self.item_ctr += 1;
toplevels: Default::default(), DockItem {
desktop_info: new_dock_item, id: self.item_ctr,
} toplevels: Default::default(),
} desktop_info: de,
}) original_app_id: original_id.clone(),
.collect(); }
}
})
.collect();
} }
Message::CloseRequested(id) => { Message::CloseRequested(id) => {
if Some(id) == self.popup.as_ref().map(|p| p.0) { if Some(id) == self.popup.as_ref().map(|p| p.0) {
@ -1412,49 +1414,45 @@ impl cosmic::Application for CosmicAppList {
fn view_window(&self, id: window::Id) -> Element<Message> { fn view_window(&self, id: window::Id) -> Element<Message> {
if let Some((_, item, _)) = self.dnd_source.as_ref().filter(|s| s.0 == id) { if let Some((_, item, _)) = self.dnd_source.as_ref().filter(|s| s.0 == id) {
item.desktop_info IconSource::from_unknown(item.desktop_info.icon().unwrap_or_default())
.icon
.as_cosmic_icon() .as_cosmic_icon()
.size(self.core.applet.suggested_size(false).0) .size(self.core.applet.suggested_size(false).0)
.into() .into()
} else if let Some((_popup_id, id, popup_type)) = self.popup.as_ref().filter(|p| id == p.0) } else if let Some((_popup_id, id, popup_type)) = self.popup.as_ref().filter(|p| id == p.0)
{ {
let Some(DockItem { let (
toplevels, DockItem {
desktop_info, toplevels,
.. desktop_info,
}) = self ..
.pinned_list },
.iter() is_pinned,
.chain(self.active_list.iter()) ) = match self.pinned_list.iter().find(|i| i.id == *id) {
.find(|i| i.id == *id) Some(e) => (e, true),
else { None => match self.active_list.iter().find(|i| i.id == *id) {
return iced::widget::text("").into(); Some(e) => (e, false),
None => return iced::widget::text("").into(),
},
}; };
match popup_type { match popup_type {
PopupType::RightClickMenu => { PopupType::RightClickMenu => {
let is_pinned = self
.config
.favorites
.iter()
.any(|x| app_id_or_fallback_matches(x, desktop_info));
let mut content = column![container( let mut content = column![container(
iced::widget::text(&desktop_info.name) iced::widget::text(&desktop_info.name(&self.locales).unwrap_or_default())
.horizontal_alignment(Horizontal::Center) .horizontal_alignment(Horizontal::Center)
) )
.padding(menu_control_padding()),] .padding(menu_control_padding()),]
.padding([8, 0]) .padding([8, 0])
.align_items(Alignment::Center); .align_items(Alignment::Center);
if let Some(exec) = desktop_info.exec.clone() { if let Some(exec) = desktop_info.exec() {
if !toplevels.is_empty() { if !toplevels.is_empty() {
content = content.push( content = content.push(
menu_button(iced::widget::text(fl!("new-window"))) menu_button(iced::widget::text(fl!("new-window")))
.on_press(Message::Exec(exec, None)), .on_press(Message::Exec(exec.to_string(), None)),
); );
} else if let Some(gpus) = self.gpus.as_ref() { } else if let Some(gpus) = self.gpus.as_ref() {
let default_idx = if desktop_info.prefers_dgpu { let default_idx = if desktop_info.prefers_non_default_gpu() {
gpus.iter().position(|gpu| !gpu.default).unwrap_or(0) gpus.iter().position(|gpu| !gpu.default).unwrap_or(0)
} else { } else {
gpus.iter().position(|gpu| gpu.default).unwrap_or(0) gpus.iter().position(|gpu| gpu.default).unwrap_or(0)
@ -1470,13 +1468,13 @@ impl cosmic::Application for CosmicAppList {
String::new() String::new()
} }
))) )))
.on_press(Message::Exec(exec.clone(), Some(i))), .on_press(Message::Exec(exec.to_string(), Some(i))),
); );
} }
} else { } else {
content = content.push( content = content.push(
menu_button(iced::widget::text(fl!("run"))) menu_button(iced::widget::text(fl!("run")))
.on_press(Message::Exec(exec, None)), .on_press(Message::Exec(exec.to_string(), None)),
); );
} }
content = content.push(divider::horizontal::default()); content = content.push(divider::horizontal::default());
@ -1501,12 +1499,12 @@ impl cosmic::Application for CosmicAppList {
if is_pinned { if is_pinned {
content = content.push( content = content.push(
menu_button(iced::widget::text(fl!("unpin"))) menu_button(iced::widget::text(fl!("unpin")))
.on_press(Message::UnpinApp(desktop_info.id.clone())), .on_press(Message::UnpinApp(*id)),
) )
} else if let Some(_) = desktop_info.exec.clone() { } else if desktop_info.exec().is_some() {
content = content.push( content = content.push(
menu_button(iced::widget::text(fl!("pin"))) menu_button(iced::widget::text(fl!("pin")))
.on_press(Message::PinApp(desktop_info.id.clone())), .on_press(Message::PinApp(*id)),
) )
} }
@ -1514,11 +1512,11 @@ impl cosmic::Application for CosmicAppList {
0 => content, 0 => content,
1 => content.push( 1 => content.push(
menu_button(iced::widget::text(fl!("quit"))) menu_button(iced::widget::text(fl!("quit")))
.on_press(Message::Quit(desktop_info.id.clone())), .on_press(Message::Quit(desktop_info.id().to_string())),
), ),
_ => content.push( _ => content.push(
menu_button(iced::widget::text(&fl!("quit-all"))) menu_button(iced::widget::text(&fl!("quit-all")))
.on_press(Message::Quit(desktop_info.id.clone())), .on_press(Message::Quit(desktop_info.id().to_string())),
), ),
}; };
self.core.applet.popup_container(content).into() self.core.applet.popup_container(content).into()
@ -1540,7 +1538,7 @@ impl cosmic::Application for CosmicAppList {
Message::Toggle(handle.clone()), Message::Toggle(handle.clone()),
title, title,
10.0, 10.0,
self.currently_active_toplevel().contains(&handle), self.currently_active_toplevel().contains(handle),
)); ));
} }
self.core.applet.popup_container(content).into() self.core.applet.popup_container(content).into()
@ -1559,7 +1557,7 @@ impl cosmic::Application for CosmicAppList {
Message::Toggle(handle.clone()), Message::Toggle(handle.clone()),
title, title,
10.0, 10.0,
self.currently_active_toplevel().contains(&handle), self.currently_active_toplevel().contains(handle),
)); ));
} }
self.core.applet.popup_container(content).into() self.core.applet.popup_container(content).into()
@ -1659,21 +1657,18 @@ impl CosmicAppList {
} }
} }
fn launch_on_preferred_gpu( fn launch_on_preferred_gpu(desktop_info: &DesktopEntry, gpus: Option<&[Gpu]>) -> Option<Message> {
desktop_info: &DesktopEntryData, let Some(exec) = desktop_info.exec() else {
gpus: Option<&[Gpu]>,
) -> Option<Message> {
let Some(exec) = desktop_info.exec.clone() else {
return None; return None;
}; };
let gpu_idx = gpus.map(|gpus| { let gpu_idx = gpus.map(|gpus| {
if desktop_info.prefers_dgpu { if desktop_info.prefers_non_default_gpu() {
gpus.iter().position(|gpu| !gpu.default).unwrap_or(0) gpus.iter().position(|gpu| !gpu.default).unwrap_or(0)
} else { } else {
gpus.iter().position(|gpu| gpu.default).unwrap_or(0) gpus.iter().position(|gpu| gpu.default).unwrap_or(0)
} }
}); });
Some(Message::Exec(exec, gpu_idx)) Some(Message::Exec(exec.to_string(), gpu_idx))
} }

View file

@ -40,7 +40,7 @@ impl AppListConfig {
} }
} }
pub fn remove_pinned(&mut self, id: String, config: &Config) { pub fn remove_pinned(&mut self, id: &str, config: &Config) {
if let Some(pos) = self.favorites.iter().position(|e| e == &id) { if let Some(pos) = self.favorites.iter().position(|e| e == &id) {
self.favorites.remove(pos); self.favorites.remove(pos);
let _ = self.write_entry(config); let _ = self.write_entry(config);