Skip to content

Commit

Permalink
libcosmic: Add desktop-file helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
Drakulix committed Jan 25, 2024
1 parent 3aef16b commit 1fc5463
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 1 deletion.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pipewire = ["ashpd?/pipewire"]
process = ["dep:nix"]
# Use rfd for file dialogs
rfd = ["dep:rfd"]
# Enables desktop files helpers
desktop = ["process", "dep:freedesktop-desktop-entry", "dep:shlex"]
# Enables keycode serialization
serde-keycode = ["iced_core/serde"]
# Prevents multiple separate process instances.
Expand Down Expand Up @@ -78,6 +80,8 @@ zbus = {version = "3.14.1", default-features = false, optional = true}

[target.'cfg(unix)'.dependencies]
freedesktop-icons = "0.2.5"
freedesktop-desktop-entry = { version = "0.5.0", optional = true }
shlex = { version = "1.3.0", optional = true }

[dependencies.cosmic-theme]
path = "cosmic-theme"
Expand Down Expand Up @@ -106,7 +110,6 @@ path = "./iced/futures"

[dependencies.iced_accessibility]
path = "./iced/accessibility"

optional = true

[dependencies.iced_tiny_skia]
Expand Down
212 changes: 212 additions & 0 deletions src/desktop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
pub use freedesktop_desktop_entry::DesktopEntry;
use std::{
borrow::Cow,
path::{Path, PathBuf},
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IconSource {
Name(String),
Path(PathBuf),
}

impl IconSource {
pub fn from_unknown(icon: &str) -> Self {
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 {
Self::Name(name) => crate::widget::icon::from_name(name.as_str())
.size(128)
.fallback(Some(crate::widget::icon::IconFallback::Names(vec![
"application-default".into(),
"application-x-executable".into(),
])))
.into(),
Self::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())
}
}

#[derive(Debug, Clone, PartialEq)]
pub struct DesktopAction {
pub name: String,
pub exec: String,
}

#[derive(Debug, Clone, PartialEq, Default)]
pub struct DesktopEntryData {
pub id: String,
pub name: String,
pub wm_class: Option<String>,
pub exec: Option<String>,
pub icon: IconSource,
pub path: Option<PathBuf>,
pub categories: String,
pub desktop_actions: Vec<DesktopAction>,
pub prefers_dgpu: bool,
}

pub fn load_applications<'a>(
locale: impl Into<Option<&'a str>>,
include_no_display: bool,
) -> Vec<DesktopEntryData> {
load_applications_filtered(locale, |de| include_no_display || !de.no_display())
}

pub fn load_applications_for_app_ids<'a, 'b>(
locale: impl Into<Option<&'a str>>,
app_ids: impl AsRef<[&'b str]>,
fill_missing_ones: bool,
) -> Vec<DesktopEntryData> {
let mut app_ids = Vec::from(app_ids.as_ref());
let mut applications = load_applications_filtered(locale, |de| {
if let Some(i) = app_ids
.iter()
.position(|id| id == &de.appid || id.eq(&de.startup_wm_class().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
}

pub fn load_applications_filtered<'a, F: FnMut(&DesktopEntry) -> bool>(
locale: impl Into<Option<&'a str>>,
mut filter: F,
) -> Vec<DesktopEntryData> {
let locale = locale.into();

freedesktop_desktop_entry::Iter::new(freedesktop_desktop_entry::default_paths())
.filter_map(|path| {
std::fs::read_to_string(&path).ok().and_then(|input| {
DesktopEntry::decode(&path, &input).ok().and_then(|de| {
if !filter(&de) {
return None;
}

Some(DesktopEntryData::from_desktop_entry(locale, path.clone(), de))
})
})
})
.collect()
}

pub fn load_desktop_file<'a>(
locale: impl Into<Option<&'a str>>,
path: impl AsRef<Path>,
) -> Option<DesktopEntryData> {
let path = path.as_ref();
std::fs::read_to_string(path).ok().and_then(|input| {
DesktopEntry::decode(path, &input)
.ok()
.map(|de| DesktopEntryData::from_desktop_entry(locale, PathBuf::from(path), de))
})
}

impl DesktopEntryData {
fn from_desktop_entry<'a>(
locale: impl Into<Option<&'a str>>,
path: impl Into<Option<PathBuf>>,
de: DesktopEntry,
) -> DesktopEntryData {
let locale = locale.into();

let name = de
.name(locale)
.unwrap_or(Cow::Borrowed(de.appid))
.to_string();

// check if absolute path exists and otherwise treat it as a name
let icon = 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 {
id: de.appid.to_string(),
wm_class: de.startup_wm_class().map(ToString::to_string),
exec: de.exec().map(ToString::to_string),
name,
icon,
path: path.into(),
categories: de.categories().unwrap_or_default().to_string(),
desktop_actions: de
.actions()
.map(|actions| {
actions
.split(';')
.filter_map(|action| {
let name = de.action_entry_localized(action, "Name", locale);
let exec = de.action_entry(action, "Exec");
if let (Some(name), Some(exec)) = (name, exec) {
Some(DesktopAction {
name: name.to_string(),
exec: exec.to_string(),
})
} else {
None
}
})
.collect::<Vec<_>>()
})
.unwrap_or_default(),
prefers_dgpu: de.prefers_non_default_gpu(),
}
}
}

pub fn spawn_desktop_exec<S, I, K, V>(
exec: S,
env_vars: I,
)
where
S: AsRef<str>,
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,

{
let mut exec = shlex::Shlex::new(exec.as_ref());
let mut cmd = match exec.next() {
Some(cmd) if !cmd.contains('=') => std::process::Command::new(cmd),
_ => return,
};

for arg in exec {
// TODO handle "%" args here if necessary?
if !arg.starts_with('%') {
cmd.arg(arg);
}
}

cmd.envs(env_vars);

crate::process::spawn(cmd)
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ pub use iced_winit;
pub mod icon_theme;
pub mod keyboard_nav;

#[cfg(feature = "desktop")]
pub mod desktop;
#[cfg(feature = "process")]
pub mod process;

Expand Down

0 comments on commit 1fc5463

Please sign in to comment.