fix(desktop): matching the wrong desktop enrties and not getting icons
This commit is contained in:
parent
1486569481
commit
9b9600a5d6
3 changed files with 86 additions and 135 deletions
|
|
@ -100,6 +100,7 @@ apply = "0.3.0"
|
||||||
ashpd = { version = "0.9.2", default-features = false, optional = true }
|
ashpd = { version = "0.9.2", default-features = false, optional = true }
|
||||||
async-fs = { version = "2.1", optional = true }
|
async-fs = { version = "2.1", optional = true }
|
||||||
async-std = { version = "1.13", optional = true }
|
async-std = { version = "1.13", optional = true }
|
||||||
|
auto_enums = "0.8.7"
|
||||||
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "178eb0b", optional = true }
|
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "178eb0b", optional = true }
|
||||||
chrono = "0.4.40"
|
chrono = "0.4.40"
|
||||||
cosmic-config = { path = "cosmic-config" }
|
cosmic-config = { path = "cosmic-config" }
|
||||||
|
|
@ -131,8 +132,11 @@ url = "2.5.4"
|
||||||
zbus = { version = "4.4.0", default-features = false, optional = true }
|
zbus = { version = "4.4.0", default-features = false, optional = true }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" }
|
freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons", branch = "icon-fixes" }
|
||||||
freedesktop-desktop-entry = { version = "0.7.9", optional = true }
|
# freedesktop-icons = { package = "cosmic-freedesktop-icons", path = "../../pop/freedesktop-icons" }
|
||||||
|
freedesktop-desktop-entry = { git = "https://github.com/pop-os/freedesktop-desktop-entry", branch = "appid-match", optional = true }
|
||||||
|
# freedesktop-desktop-entry = { version = "0.7.9", optional = true }
|
||||||
|
# freedesktop-desktop-entry = { path = "../../pop/freedesktop-desktop-entry", optional = true }
|
||||||
shlex = { version = "1.3.0", optional = true }
|
shlex = { version = "1.3.0", optional = true }
|
||||||
|
|
||||||
[dependencies.cosmic-theme]
|
[dependencies.cosmic-theme]
|
||||||
|
|
|
||||||
206
src/desktop.rs
206
src/desktop.rs
|
|
@ -1,47 +1,32 @@
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
pub use freedesktop_desktop_entry::DesktopEntry;
|
pub use freedesktop_desktop_entry as fde;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
pub use mime::Mime;
|
pub use mime::Mime;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::{borrow::Cow, ffi::OsStr};
|
use std::{borrow::Cow, ffi::OsStr};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
pub trait IconSourceExt {
|
||||||
pub enum IconSource {
|
fn as_cosmic_icon(&self) -> crate::widget::icon::Icon;
|
||||||
Name(String),
|
|
||||||
Path(PathBuf),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IconSource {
|
impl IconSourceExt for fde::IconSource {
|
||||||
pub fn from_unknown(icon: &str) -> Self {
|
fn as_cosmic_icon(&self) -> crate::widget::icon::Icon {
|
||||||
let icon_path = Path::new(icon);
|
|
||||||
if icon_path.is_absolute() && icon_path.exists() {
|
|
||||||
Self::Path(icon_path.into())
|
|
||||||
} else {
|
|
||||||
Self::Name(icon.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_cosmic_icon(&self) -> crate::widget::icon::Icon {
|
|
||||||
match self {
|
match self {
|
||||||
Self::Name(name) => crate::widget::icon::from_name(name.as_str())
|
fde::IconSource::Name(name) => crate::widget::icon::from_name(name.as_str())
|
||||||
.size(128)
|
.size(128)
|
||||||
.fallback(Some(crate::widget::icon::IconFallback::Names(vec![
|
.fallback(Some(crate::widget::icon::IconFallback::Names(vec![
|
||||||
"application-default".into(),
|
"application-default".into(),
|
||||||
"application-x-executable".into(),
|
"application-x-executable".into(),
|
||||||
])))
|
])))
|
||||||
.into(),
|
.into(),
|
||||||
Self::Path(path) => crate::widget::icon(crate::widget::icon::from_path(path.clone())),
|
fde::IconSource::Path(path) => {
|
||||||
|
crate::widget::icon(crate::widget::icon::from_path(path.clone()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for IconSource {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Name("application-default".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct DesktopAction {
|
pub struct DesktopAction {
|
||||||
|
|
@ -56,7 +41,7 @@ pub struct DesktopEntryData {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub wm_class: Option<String>,
|
pub wm_class: Option<String>,
|
||||||
pub exec: Option<String>,
|
pub exec: Option<String>,
|
||||||
pub icon: IconSource,
|
pub icon: fde::IconSource,
|
||||||
pub path: Option<PathBuf>,
|
pub path: Option<PathBuf>,
|
||||||
pub categories: Vec<String>,
|
pub categories: Vec<String>,
|
||||||
pub desktop_actions: Vec<DesktopAction>,
|
pub desktop_actions: Vec<DesktopAction>,
|
||||||
|
|
@ -66,127 +51,94 @@ pub struct DesktopEntryData {
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
pub fn load_applications<'a>(
|
pub fn load_applications<'a>(
|
||||||
locale: impl Into<Option<&'a str>>,
|
locales: &'a [String],
|
||||||
include_no_display: bool,
|
include_no_display: bool,
|
||||||
) -> Vec<DesktopEntryData> {
|
) -> impl Iterator<Item = DesktopEntryData> + 'a {
|
||||||
load_applications_filtered(locale, |de| include_no_display || !de.no_display())
|
fde::Iter::new(fde::default_paths())
|
||||||
|
.filter_map(move |p| fde::DesktopEntry::from_path(p, Some(locales)).ok())
|
||||||
|
.filter(move |de| include_no_display || !de.no_display())
|
||||||
|
.map(move |de| DesktopEntryData::from_desktop_entry(locales, de))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create an iterator which filters desktop entries by app IDs.
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
pub fn app_id_or_fallback_matches(app_id: &str, entry: &DesktopEntryData) -> bool {
|
#[auto_enums::auto_enum(Iterator)]
|
||||||
let lowercase_wm_class = entry.wm_class.as_ref().map(|s| s.to_lowercase());
|
pub fn load_applications_for_app_ids<'a>(
|
||||||
|
iter: impl Iterator<Item = fde::DesktopEntry> + 'a,
|
||||||
app_id == entry.id
|
locales: &'a [String],
|
||||||
|| Some(app_id.to_lowercase()) == lowercase_wm_class
|
app_ids: Vec<&'a str>,
|
||||||
|| app_id.to_lowercase() == entry.name.to_lowercase()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
pub fn load_applications_for_app_ids<'a, 'b>(
|
|
||||||
locale: impl Into<Option<&'a str>>,
|
|
||||||
app_ids: impl Iterator<Item = &'b str>,
|
|
||||||
fill_missing_ones: bool,
|
fill_missing_ones: bool,
|
||||||
include_no_display: bool,
|
include_no_display: bool,
|
||||||
) -> Vec<DesktopEntryData> {
|
) -> impl Iterator<Item = DesktopEntryData> + 'a {
|
||||||
let mut app_ids = app_ids.collect::<Vec<_>>();
|
let app_ids = std::rc::Rc::new(std::cell::RefCell::new(app_ids));
|
||||||
let mut applications = load_applications_filtered(locale, |de| {
|
let app_ids_ = app_ids.clone();
|
||||||
if !include_no_display && de.no_display() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If appid matches, or startup_wm_class matches...
|
|
||||||
if let Some(i) = app_ids.iter().position(|id| {
|
|
||||||
id == &de.appid
|
|
||||||
|| id
|
|
||||||
.to_lowercase()
|
|
||||||
.eq(&de.startup_wm_class().unwrap_or_default().to_lowercase())
|
|
||||||
}) {
|
|
||||||
app_ids.remove(i);
|
|
||||||
true
|
|
||||||
// Fallback: If the name matches...
|
|
||||||
} else if let Some(i) = app_ids.iter().position(|id| {
|
|
||||||
de.name::<&str>(&[])
|
|
||||||
.map(|n| n.to_lowercase() == id.to_lowercase())
|
|
||||||
.unwrap_or_default()
|
|
||||||
}) {
|
|
||||||
app_ids.remove(i);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if fill_missing_ones {
|
|
||||||
applications.extend(app_ids.into_iter().map(|app_id| DesktopEntryData {
|
|
||||||
id: app_id.to_string(),
|
|
||||||
name: app_id.to_string(),
|
|
||||||
icon: IconSource::default(),
|
|
||||||
..Default::default()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
applications
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
let applications = iter
|
||||||
pub fn load_applications_filtered<'a, F: FnMut(&DesktopEntry) -> bool>(
|
.filter(move |de| {
|
||||||
locale: impl Into<Option<&'a str>>,
|
if !include_no_display && de.no_display() {
|
||||||
mut filter: F,
|
return false;
|
||||||
) -> Vec<DesktopEntryData> {
|
}
|
||||||
let locale = locale.into();
|
|
||||||
let locale_arr: Option<Vec<_>> = locale.map(|l| vec![l]);
|
|
||||||
freedesktop_desktop_entry::Iter::new(freedesktop_desktop_entry::default_paths())
|
|
||||||
.filter_map(|path| {
|
|
||||||
DesktopEntry::from_path(&path, locale_arr.as_deref())
|
|
||||||
.ok()
|
|
||||||
.and_then(|de| {
|
|
||||||
if !filter(&de) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(DesktopEntryData::from_desktop_entry(
|
// Search by ID first
|
||||||
locale,
|
app_ids
|
||||||
path.clone(),
|
.borrow()
|
||||||
de,
|
.iter()
|
||||||
))
|
.position(|id| de.matches_id(fde::unicase::Ascii::new(*id)))
|
||||||
|
// Then fall back to search by name
|
||||||
|
.or_else(|| {
|
||||||
|
app_ids
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.position(|id| de.matches_name(fde::unicase::Ascii::new(*id)))
|
||||||
})
|
})
|
||||||
|
// Remove the app ID if found
|
||||||
|
.map(|i| {
|
||||||
|
app_ids.borrow_mut().remove(i);
|
||||||
|
true
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
})
|
})
|
||||||
.collect()
|
.map(move |de| DesktopEntryData::from_desktop_entry(locales, de));
|
||||||
|
|
||||||
|
if fill_missing_ones {
|
||||||
|
applications.chain(
|
||||||
|
std::iter::once_with(move || {
|
||||||
|
std::mem::take(&mut *app_ids_.borrow_mut())
|
||||||
|
.into_iter()
|
||||||
|
.map(|app_id| DesktopEntryData {
|
||||||
|
id: app_id.to_string(),
|
||||||
|
name: app_id.to_string(),
|
||||||
|
icon: fde::IconSource::default(),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
applications
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
pub fn load_desktop_file<'a>(
|
pub fn load_desktop_file<'a>(locales: &'a [String], path: PathBuf) -> Option<DesktopEntryData> {
|
||||||
locale: impl Into<Option<&'a str>>,
|
fde::DesktopEntry::from_path(path, Some(locales))
|
||||||
path: impl AsRef<Path>,
|
|
||||||
) -> Option<DesktopEntryData> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
let locale = locale.into();
|
|
||||||
let locale_arr: Option<Vec<_>> = locale.clone().map(|l| vec![l]);
|
|
||||||
|
|
||||||
DesktopEntry::from_path(&path, locale_arr.as_deref())
|
|
||||||
.ok()
|
.ok()
|
||||||
.map(|de| DesktopEntryData::from_desktop_entry(locale, PathBuf::from(path), de))
|
.map(|de| DesktopEntryData::from_desktop_entry(locales, de))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
impl DesktopEntryData {
|
impl DesktopEntryData {
|
||||||
fn from_desktop_entry<'a>(
|
pub fn from_desktop_entry<'a>(
|
||||||
locale: impl Into<Option<&'a str>>,
|
locales: &'a [String],
|
||||||
path: impl Into<Option<PathBuf>>,
|
de: fde::DesktopEntry,
|
||||||
de: DesktopEntry,
|
|
||||||
) -> DesktopEntryData {
|
) -> DesktopEntryData {
|
||||||
let locale = locale.into();
|
|
||||||
let locale_arr: Option<Vec<_>> = locale.map(|l| vec![l]);
|
|
||||||
let name = de
|
let name = de
|
||||||
.name(locale_arr.as_deref().unwrap_or_default())
|
.name(locales)
|
||||||
.unwrap_or(Cow::Borrowed(&de.appid))
|
.unwrap_or(Cow::Borrowed(&de.appid))
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
// check if absolute path exists and otherwise treat it as a name
|
// check if absolute path exists and otherwise treat it as a name
|
||||||
let icon = de.icon().unwrap_or(&de.appid);
|
let icon = fde::IconSource::from_unknown(de.icon().unwrap_or(&de.appid));
|
||||||
let icon_path = Path::new(icon);
|
|
||||||
let icon = if icon_path.is_absolute() && icon_path.exists() {
|
|
||||||
IconSource::Path(icon_path.into())
|
|
||||||
} else {
|
|
||||||
IconSource::Name(icon.into())
|
|
||||||
};
|
|
||||||
|
|
||||||
DesktopEntryData {
|
DesktopEntryData {
|
||||||
id: de.appid.to_string(),
|
id: de.appid.to_string(),
|
||||||
|
|
@ -194,7 +146,6 @@ impl DesktopEntryData {
|
||||||
exec: de.exec().map(ToString::to_string),
|
exec: de.exec().map(ToString::to_string),
|
||||||
name,
|
name,
|
||||||
icon,
|
icon,
|
||||||
path: path.into(),
|
|
||||||
categories: de
|
categories: de
|
||||||
.categories()
|
.categories()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
|
|
@ -207,11 +158,7 @@ impl DesktopEntryData {
|
||||||
actions
|
actions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|action| {
|
.filter_map(|action| {
|
||||||
let name = de.action_entry_localized(
|
let name = de.action_entry_localized(action, "Name", locales);
|
||||||
action,
|
|
||||||
"Name",
|
|
||||||
locale_arr.as_deref().unwrap_or_default(),
|
|
||||||
);
|
|
||||||
let exec = de.action_entry(action, "Exec");
|
let exec = de.action_entry(action, "Exec");
|
||||||
if let (Some(name), Some(exec)) = (name, exec) {
|
if let (Some(name), Some(exec)) = (name, exec) {
|
||||||
Some(DesktopAction {
|
Some(DesktopAction {
|
||||||
|
|
@ -235,6 +182,7 @@ impl DesktopEntryData {
|
||||||
})
|
})
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
prefers_dgpu: de.prefers_non_default_gpu(),
|
prefers_dgpu: de.prefers_non_default_gpu(),
|
||||||
|
path: Some(de.path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,17 @@
|
||||||
#[cfg(feature = "xdg-portal")]
|
#[cfg(feature = "xdg-portal")]
|
||||||
pub mod portal;
|
pub mod portal;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
use cosmic_theme::Spacing;
|
|
||||||
use cosmic_theme::ThemeMode;
|
|
||||||
pub use style::*;
|
|
||||||
|
|
||||||
use cosmic_config::CosmicConfigEntry;
|
use cosmic_config::CosmicConfigEntry;
|
||||||
use cosmic_config::config_subscription;
|
use cosmic_config::config_subscription;
|
||||||
use cosmic_theme::Component;
|
use cosmic_theme::Component;
|
||||||
use cosmic_theme::LayeredTheme;
|
use cosmic_theme::LayeredTheme;
|
||||||
|
use cosmic_theme::Spacing;
|
||||||
|
use cosmic_theme::ThemeMode;
|
||||||
use iced_futures::Subscription;
|
use iced_futures::Subscription;
|
||||||
use iced_runtime::{Appearance, DefaultStyle};
|
use iced_runtime::{Appearance, DefaultStyle};
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
pub use style::*;
|
||||||
|
|
||||||
#[cfg(feature = "dbus-config")]
|
#[cfg(feature = "dbus-config")]
|
||||||
use cosmic_config::dbus;
|
use cosmic_config::dbus;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue