diff --git a/Cargo.lock b/Cargo.lock index 3e5c9044..ea98988a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -904,6 +904,7 @@ dependencies = [ "anyhow", "cosmic-client-toolkit", "cosmic-protocols", + "freedesktop-desktop-entry 0.6.0", "futures", "i18n-embed 0.14.1", "i18n-embed-fl 0.8.0", @@ -1205,7 +1206,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -1227,7 +1228,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "quote", "syn 1.0.109", @@ -1274,7 +1275,7 @@ name = "cosmic-panel-button" version = "0.1.0" dependencies = [ "cosmic-config", - "freedesktop-desktop-entry", + "freedesktop-desktop-entry 0.5.2", "libcosmic", "serde", "tracing", @@ -1343,7 +1344,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "almost", "cosmic-config", @@ -2235,6 +2236,21 @@ dependencies = [ "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]] name = "freedesktop-icons" version = "0.2.6" @@ -2767,7 +2783,7 @@ dependencies = [ [[package]] name = "iced" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "dnd", "iced_accessibility", @@ -2785,7 +2801,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "accesskit", "accesskit_unix", @@ -2794,7 +2810,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "bitflags 2.5.0", "dnd", @@ -2816,7 +2832,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "futures", "iced_core", @@ -2829,7 +2845,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "bitflags 2.5.0", "bytemuck", @@ -2853,7 +2869,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2865,7 +2881,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "dnd", "iced_accessibility", @@ -2879,7 +2895,7 @@ dependencies = [ [[package]] name = "iced_sctk" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "enum-repr", "float-cmp", @@ -2905,7 +2921,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "iced_core", "once_cell", @@ -2915,7 +2931,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "bytemuck", "cosmic-text", @@ -2932,7 +2948,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "bitflags 2.5.0", "bytemuck", @@ -2958,7 +2974,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "dnd", "iced_renderer", @@ -3694,7 +3710,7 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#2af549f5b15a83b3973f3291d1107a419f1e360a" +source = "git+https://github.com/pop-os/libcosmic#173ddca60fdb26cf5b1784edfc1a78594acf7002" dependencies = [ "apply", "ashpd", @@ -3707,7 +3723,7 @@ dependencies = [ "css-color", "derive_setters", "fraction", - "freedesktop-desktop-entry", + "freedesktop-desktop-entry 0.5.2", "freedesktop-icons", "iced", "iced_core", @@ -3724,9 +3740,11 @@ dependencies = [ "palette", "rfd", "ron", + "serde", "shlex", "slotmap", "taffy", + "textdistance", "thiserror", "tokio", "tracing", @@ -5628,6 +5646,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textdistance" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d321c8576c2b47e43953e9cce236550d4cd6af0a6ce518fe084340082ca6037b" + [[package]] name = "thiserror" version = "1.0.61" diff --git a/Cargo.toml b/Cargo.toml index 07f7c898..8dfc649d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-log = "0.2.0" cosmic-config = { git = "https://github.com/pop-os/libcosmic" } serde = { version = "1.0.152", features = ["derive"] } +freedesktop-desktop-entry = "0.6.0" [profile.release] lto = "fat" @@ -65,4 +66,4 @@ ignored = ["libcosmic"] # libcosmic = { git = "https://github.com/pop-os/libcosmic//" } [patch."https://github.com/Smithay/client-toolkit"] -sctk = { git = "https://github.com/smithay/client-toolkit//", package = "smithay-client-toolkit", rev = "3bed072" } +sctk = { git = "https://github.com/smithay/client-toolkit//", package = "smithay-client-toolkit", rev = "3bed072" } \ No newline at end of file diff --git a/cosmic-app-list/Cargo.toml b/cosmic-app-list/Cargo.toml index 8e4d65f3..dcf6e0a3 100644 --- a/cosmic-app-list/Cargo.toml +++ b/cosmic-app-list/Cargo.toml @@ -34,3 +34,4 @@ tracing-subscriber.workspace = true tracing.workspace = true url = "2.5.0" zbus.workspace = true +freedesktop-desktop-entry.workspace = true diff --git a/cosmic-app-list/src/app.rs b/cosmic-app-list/src/app.rs index 19809d9d..18981971 100755 --- a/cosmic-app-list/src/app.rs +++ b/cosmic-app-list/src/app.rs @@ -21,9 +21,7 @@ use cctk::wayland_client::protocol::wl_seat::WlSeat; use cosmic::applet::cosmic_panel_config::PanelSize; use cosmic::applet::Size; use cosmic::cosmic_config::{Config, CosmicConfigEntry}; -use cosmic::desktop::{ - app_id_or_fallback_matches, load_applications_for_app_ids, DesktopEntryData, -}; +use cosmic::desktop::IconSource; use cosmic::iced; use cosmic::iced::event::listen_with; 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_space; use cosmic::iced::widget::{column, dnd_source, mouse_area, row, Column, Row}; -use cosmic::iced::window::icon; use cosmic::iced::Color; use cosmic::iced::Vector; 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::ZcosmicToplevelHandleV1; 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 iced::widget::container; use iced::Alignment; @@ -79,6 +79,7 @@ use itertools::Itertools; use rand::{thread_rng, Rng}; use std::cmp::min; use std::collections::HashMap; +use std::fs; use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; @@ -92,28 +93,6 @@ pub fn run() -> cosmic::iced::Result { cosmic::applet::run::(true, ()) } -pub fn load_applications_for_app_ids_sorted<'a, 'b>( - locale: impl Into>, - app_ids: impl Iterator + Clone, - fill_missing_ones: bool, -) -> Vec { - 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)] struct AppletIconData { icon_size: u16, @@ -154,18 +133,26 @@ impl AppletIconData { } } -#[derive(Debug, Clone, Default)] +type DockItemId = u32; + +#[derive(Debug, Clone)] struct DockItem { - id: u32, + // ID used internally in the applet. Each dock item + // have an unique id + id: DockItemId, toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo, Option)>, - 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 { fn from_mime_type(&self, mime_type: &str) -> Option> { - if mime_type == MIME_TYPE && self.desktop_info.path.is_some() { + if mime_type == MIME_TYPE { Some( - Url::from_file_path(self.desktop_info.path.as_deref().unwrap()) + Url::from_file_path(&self.desktop_info.path) .ok()? .to_string() .as_bytes() @@ -178,18 +165,6 @@ impl DataFromMimeType for DockItem { } impl DockItem { - fn new( - id: u32, - toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo, Option)>, - desktop_info: DesktopEntryData, - ) -> Self { - Self { - id, - toplevels, - desktop_info, - } - } - fn as_icon( &self, applet: &Context, @@ -209,7 +184,9 @@ impl DockItem { 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() { (0..1) @@ -288,15 +265,14 @@ impl DockItem { } else if toplevels.len() == 1 { toplevels.first().map(|t| Message::Toggle(t.0.clone())) } else { - Some(Message::TopLevelListPopup(desktop_info.id.clone())) + Some(Message::TopLevelListPopup(*id)) }) .width(Length::Shrink) .height(Length::Shrink), ) - .on_right_release(Message::Popup(desktop_info.id.clone())) + .on_right_release(Message::Popup(*id)) .on_middle_release({ - launch_on_preferred_gpu(desktop_info, gpus) - .unwrap_or_else(|| Message::Popup(desktop_info.id.clone())) + launch_on_preferred_gpu(desktop_info, gpus).unwrap_or_else(|| Message::Popup(*id)) }) .into() } else { @@ -306,7 +282,7 @@ impl DockItem { let icon_button = if dnd_source_enabled && interaction_enabled { dnd_source(icon_button) .drag_threshold(16.) - .on_drag(|_, _| Message::StartDrag(desktop_info.id.clone())) + .on_drag(|_, _| Message::StartDrag(*id)) .on_cancelled(Message::DragFinished) .on_finished(Message::DragFinished) } else { @@ -330,7 +306,7 @@ struct DndOffer { #[derive(Clone, Default)] struct CosmicAppList { core: cosmic::app::Core, - popup: Option<(window::Id, u32, PopupType)>, + popup: Option<(window::Id, DockItemId, PopupType)>, subscription_ctr: u32, item_ctr: u32, active_list: Vec, @@ -339,13 +315,14 @@ struct CosmicAppList { config: AppListConfig, wayland_sender: Option>, seat: Option, - rectangle_tracker: Option>, - rectangles: HashMap, + rectangle_tracker: Option>, + rectangles: HashMap, dnd_offer: Option, is_listening_for_dnd: bool, gpus: Option>, active_workspaces: Vec, output_list: HashMap, + locales: Vec, } #[derive(Clone, PartialEq)] @@ -358,10 +335,10 @@ pub enum PopupType { #[derive(Debug, Clone)] enum Message { Wayland(WaylandUpdate), - PinApp(String), - UnpinApp(String), - Popup(String), - TopLevelListPopup(String), + PinApp(DockItemId), + UnpinApp(DockItemId), + Popup(DockItemId), + TopLevelListPopup(DockItemId), GpuRequest(Option>), CloseRequested(window::Id), ClosePopup, @@ -372,8 +349,8 @@ enum Message { Ignore, NewSeat(WlSeat), RemovedSeat(WlSeat), - Rectangle(RectangleUpdate), - StartDrag(String), // id of the DockItem + Rectangle(RectangleUpdate), + StartDrag(DockItemId), DragFinished, DndEnter(f32, f32), DndExit, @@ -446,7 +423,7 @@ async fn try_get_gpus() -> Option> { pub fn menu_button<'a, Message>( content: impl Into>, ) -> cosmic::widget::Button<'a, Message> { - cosmic::widget::Button::new(content) + cosmic::widget::button(content) .style(Button::AppletMenu) .padding(menu_control_padding()) .width(Length::Fill) @@ -466,7 +443,7 @@ where Msg: 'static + Clone, { let border = 1.0; - cosmic::widget::Button::new( + cosmic::widget::button( container( column![ 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(ids: &[I], locales: &[L]) -> Vec> +where + I: AsRef, + L: AsRef, +{ + let srcs = fde::Iter::new(fde::default_paths()) + .filter_map(|p| fs::read_to_string(&p).ok().and_then(|e| Some((p, e)))) + .collect::>(); + + let entries = srcs + .iter() + .filter_map(|(p, data)| DesktopEntry::from_str(p, data, locales).ok()) + .collect::>(); + + 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 { let theme = cosmic::theme::active(); let cosmic = theme.cosmic(); @@ -618,28 +622,30 @@ impl cosmic::Application for CosmicAppList { .ok() .and_then(|c| AppListConfig::get_entry(&c).ok()) .unwrap_or_default(); - let mut self_ = Self { + + let locales = get_languages_from_env(); + + let mut app_list = Self { core, - pinned_list: load_applications_for_app_ids_sorted( - None, - config.favorites.iter().map(|s| &**s), - true, - ) - .into_iter() - .enumerate() - .map(|(pinned_ctr, e)| DockItem { - id: pinned_ctr as u32, - toplevels: Default::default(), - desktop_info: e, - }) - .collect(), + 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, ..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| { cosmic::app::Message::App(Message::GpuRequest(gpus)) }), @@ -667,7 +673,7 @@ impl cosmic::Application for CosmicAppList { .active_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) { Some(r) => r, @@ -711,7 +717,7 @@ impl cosmic::Application for CosmicAppList { .active_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 { if let Some(tx) = self.wayland_sender.as_ref() { @@ -770,33 +776,29 @@ impl cosmic::Application for CosmicAppList { return get_popup(popup_settings); } } + Message::PinApp(id) => { - if let Some(i) = self - .active_list - .iter() - .position(|t| t.desktop_info.id == id) - { + if let Some(i) = self.active_list.iter().position(|t| t.id == id) { 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.config - .add_pinned(id, &Config::new(APP_ID, AppListConfig::VERSION).unwrap()); if let Some((popup_id, _toplevel, _)) = self.popup.take() { return destroy_popup(popup_id); } } Message::UnpinApp(id) => { - self.config.remove_pinned( - id.clone(), - &Config::new(APP_ID, AppListConfig::VERSION).unwrap(), - ); - if let Some(i) = self - .pinned_list - .iter() - .position(|t| t.desktop_info.id == id) - { + if let Some(i) = self.pinned_list.iter().position(|t| t.id == id) { 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); if !entry.toplevels.is_empty() { self.active_list.push(entry); @@ -833,7 +835,7 @@ impl cosmic::Application for CosmicAppList { .active_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 { if let Some(tx) = self.wayland_sender.as_ref() { @@ -852,21 +854,17 @@ impl cosmic::Application for CosmicAppList { .active_list .iter() .find_map(|t| { - if t.desktop_info.id == id { + if t.id == id { Some((false, t.clone())) } else { None } }) .or_else(|| { - if let Some(pos) = self - .pinned_list - .iter() - .position(|t| t.desktop_info.id == id) - { + if let Some(pos) = self.pinned_list.iter().position(|t| t.id == id) { let t = self.pinned_list.remove(pos); self.config.remove_pinned( - t.desktop_info.id.clone(), + &t.original_app_id, &Config::new(APP_ID, AppListConfig::VERSION).unwrap(), ); Some((true, t)) @@ -896,7 +894,7 @@ impl cosmic::Application for CosmicAppList { .pinned_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() { self.item_ctr += 1; @@ -959,9 +957,14 @@ impl cosmic::Application for CosmicAppList { } Message::DndData(file_path) => { 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; - *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 .active_list .iter() - .position(|DockItem { desktop_info, .. }| { - desktop_info.id == dock_item.desktop_info.id - }) + .position(|de| de.original_app_id == dock_item.original_app_id) .map(|pos| (pos, false)) .or_else(|| { self.pinned_list .iter() - .position(|DockItem { desktop_info, .. }| { - desktop_info.id == dock_item.desktop_info.id - }) + .position(|de| de.original_app_id == dock_item.original_app_id) .map(|pos| (pos, true)) }) { @@ -1000,13 +999,13 @@ impl cosmic::Application for CosmicAppList { }; 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 .insert(index.min(self.pinned_list.len()), dock_item); self.config.update_pinned( self.pinned_list .iter() - .map(|dock_item| dock_item.desktop_info.id.clone()) + .map(|dock_item| dock_item.original_app_id.clone()) .collect(), &Config::new(APP_ID, AppListConfig::VERSION).unwrap(), ); @@ -1060,12 +1059,16 @@ impl cosmic::Application for CosmicAppList { } WaylandUpdate::Toplevel(event) => match event { 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 .active_list .iter_mut() .chain(self.pinned_list.iter_mut()) .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)); @@ -1074,18 +1077,12 @@ impl cosmic::Application for CosmicAppList { info.app_id = format!("Unknown Application {}", self.item_ctr); } 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 { id: self.item_ctr, + original_app_id: info.app_id.clone(), toplevels: vec![(handle, info, None)], - desktop_info, + desktop_info: new_desktop_info, }); } } @@ -1196,33 +1193,38 @@ impl cosmic::Application for CosmicAppList { self.config = config; // drain to active list 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 - self.pinned_list = load_applications_for_app_ids_sorted( - None, - self.config.favorites.iter().map(|s| &**s), - true, - ) - .into_iter() - .map(|new_dock_item| { - if let Some(p) = self - .active_list - .iter() - .position(|dock_item| dock_item.desktop_info.id == new_dock_item.id) - { - self.active_list.remove(p) - } else { - self.item_ctr += 1; - DockItem { - id: self.item_ctr, - toplevels: Default::default(), - desktop_info: new_dock_item, - } - } - }) - .collect(); + // pull back configured items into the favorites list + self.pinned_list = + load_desktop_entries_from_app_ids(&self.config.favorites, &self.locales) + .into_iter() + .zip(&self.config.favorites) + .map(|(de, original_id)| { + if let Some(p) = self + .active_list + .iter() + // match using heuristic id + .position(|dock_item| dock_item.desktop_info.id() == de.id()) + { + let mut d = self.active_list.remove(p); + // but use the id from the config + d.original_app_id = original_id.clone(); + d + } else { + self.item_ctr += 1; + DockItem { + id: self.item_ctr, + toplevels: Default::default(), + desktop_info: de, + original_app_id: original_id.clone(), + } + } + }) + .collect(); } Message::CloseRequested(id) => { 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 { if let Some((_, item, _)) = self.dnd_source.as_ref().filter(|s| s.0 == id) { - item.desktop_info - .icon + IconSource::from_unknown(item.desktop_info.icon().unwrap_or_default()) .as_cosmic_icon() .size(self.core.applet.suggested_size(false).0) .into() } else if let Some((_popup_id, id, popup_type)) = self.popup.as_ref().filter(|p| id == p.0) { - let Some(DockItem { - toplevels, - desktop_info, - .. - }) = self - .pinned_list - .iter() - .chain(self.active_list.iter()) - .find(|i| i.id == *id) - else { - return iced::widget::text("").into(); + let ( + DockItem { + toplevels, + desktop_info, + .. + }, + is_pinned, + ) = match self.pinned_list.iter().find(|i| i.id == *id) { + Some(e) => (e, true), + None => match self.active_list.iter().find(|i| i.id == *id) { + Some(e) => (e, false), + None => return iced::widget::text("").into(), + }, }; + match popup_type { PopupType::RightClickMenu => { - let is_pinned = self - .config - .favorites - .iter() - .any(|x| app_id_or_fallback_matches(x, desktop_info)); - 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) ) .padding(menu_control_padding()),] .padding([8, 0]) .align_items(Alignment::Center); - if let Some(exec) = desktop_info.exec.clone() { + if let Some(exec) = desktop_info.exec() { if !toplevels.is_empty() { content = content.push( 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() { - 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) } else { gpus.iter().position(|gpu| gpu.default).unwrap_or(0) @@ -1470,13 +1468,13 @@ impl cosmic::Application for CosmicAppList { String::new() } ))) - .on_press(Message::Exec(exec.clone(), Some(i))), + .on_press(Message::Exec(exec.to_string(), Some(i))), ); } } else { content = content.push( 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()); @@ -1501,12 +1499,12 @@ impl cosmic::Application for CosmicAppList { if is_pinned { content = content.push( 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( 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, 1 => content.push( 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( 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() @@ -1540,7 +1538,7 @@ impl cosmic::Application for CosmicAppList { Message::Toggle(handle.clone()), title, 10.0, - self.currently_active_toplevel().contains(&handle), + self.currently_active_toplevel().contains(handle), )); } self.core.applet.popup_container(content).into() @@ -1559,7 +1557,7 @@ impl cosmic::Application for CosmicAppList { Message::Toggle(handle.clone()), title, 10.0, - self.currently_active_toplevel().contains(&handle), + self.currently_active_toplevel().contains(handle), )); } self.core.applet.popup_container(content).into() @@ -1659,21 +1657,18 @@ impl CosmicAppList { } } -fn launch_on_preferred_gpu( - desktop_info: &DesktopEntryData, - gpus: Option<&[Gpu]>, -) -> Option { - let Some(exec) = desktop_info.exec.clone() else { +fn launch_on_preferred_gpu(desktop_info: &DesktopEntry, gpus: Option<&[Gpu]>) -> Option { + let Some(exec) = desktop_info.exec() else { return None; }; 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) } else { gpus.iter().position(|gpu| gpu.default).unwrap_or(0) } }); - Some(Message::Exec(exec, gpu_idx)) + Some(Message::Exec(exec.to_string(), gpu_idx)) } diff --git a/cosmic-app-list/src/config.rs b/cosmic-app-list/src/config.rs index ba470940..be1c8b73 100644 --- a/cosmic-app-list/src/config.rs +++ b/cosmic-app-list/src/config.rs @@ -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) { self.favorites.remove(pos); let _ = self.write_entry(config);