app_list: Use libcosmic desktop helpers

This commit is contained in:
Victoria Brekenfeld 2024-01-30 18:23:57 +00:00
parent 2a47cde1b4
commit d7a8db6a07
6 changed files with 112 additions and 248 deletions

48
Cargo.lock generated
View file

@ -792,8 +792,6 @@ dependencies = [
"anyhow",
"cosmic-client-toolkit",
"cosmic-protocols",
"freedesktop-desktop-entry",
"freedesktop-icons",
"futures",
"futures-util",
"i18n-embed 0.13.9",
@ -808,7 +806,6 @@ dependencies = [
"rust-embed 6.8.1",
"rust-embed-utils 7.8.1",
"serde",
"shlex",
"tokio",
"tracing",
"tracing-log",
@ -1036,7 +1033,7 @@ dependencies = [
[[package]]
name = "cosmic-config"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"atomicwrites",
"cosmic-config-derive",
@ -1044,17 +1041,19 @@ dependencies = [
"dirs 5.0.1",
"futures-util",
"iced_futures",
"known-folders",
"notify",
"once_cell",
"ron",
"serde",
"xdg",
"zbus",
]
[[package]]
name = "cosmic-config-derive"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"quote",
"syn 1.0.109",
@ -1164,7 +1163,7 @@ dependencies = [
[[package]]
name = "cosmic-theme"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"almost",
"cosmic-config",
@ -2689,7 +2688,7 @@ dependencies = [
[[package]]
name = "iced"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"iced_accessibility",
"iced_core",
@ -2704,7 +2703,7 @@ dependencies = [
[[package]]
name = "iced_accessibility"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"accesskit",
"accesskit_unix",
@ -2713,7 +2712,7 @@ dependencies = [
[[package]]
name = "iced_core"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"bitflags 1.3.2",
"iced_accessibility",
@ -2731,7 +2730,7 @@ dependencies = [
[[package]]
name = "iced_futures"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"futures",
"iced_core",
@ -2744,7 +2743,7 @@ dependencies = [
[[package]]
name = "iced_graphics"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"bitflags 1.3.2",
"bytemuck",
@ -2767,7 +2766,7 @@ dependencies = [
[[package]]
name = "iced_renderer"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"iced_graphics",
"iced_tiny_skia",
@ -2780,7 +2779,7 @@ dependencies = [
[[package]]
name = "iced_runtime"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"iced_accessibility",
"iced_core",
@ -2792,7 +2791,7 @@ dependencies = [
[[package]]
name = "iced_sctk"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"enum-repr",
"float-cmp",
@ -2816,7 +2815,7 @@ dependencies = [
[[package]]
name = "iced_style"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"iced_core",
"once_cell",
@ -2826,7 +2825,7 @@ dependencies = [
[[package]]
name = "iced_tiny_skia"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"bytemuck",
"cosmic-text",
@ -2844,7 +2843,7 @@ dependencies = [
[[package]]
name = "iced_wgpu"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"bitflags 1.3.2",
"bytemuck",
@ -2864,7 +2863,7 @@ dependencies = [
[[package]]
name = "iced_widget"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"iced_renderer",
"iced_runtime",
@ -3070,6 +3069,15 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "known-folders"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4397c789f2709d23cfcb703b316e0766a8d4b17db2d47b0ab096ef6047cae1d8"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "kqueue"
version = "1.0.8"
@ -3120,7 +3128,7 @@ checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "libcosmic"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic#912e8b0a4478e67ad4b3ce46e327c70dbe9887d8"
source = "git+https://github.com/pop-os/libcosmic#bf0508816b7e7098cfcc6eb16ee288207ef0cc31"
dependencies = [
"apply",
"ashpd",
@ -3132,6 +3140,7 @@ dependencies = [
"css-color",
"derive_setters",
"fraction",
"freedesktop-desktop-entry",
"freedesktop-icons",
"iced",
"iced_core",
@ -3147,6 +3156,7 @@ dependencies = [
"palette",
"rfd",
"ron",
"shlex",
"slotmap",
"taffy",
"thiserror",

View file

@ -32,7 +32,7 @@ libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = fa
"applet-token",
"tokio",
"wayland",
"process",
"desktop",
"dbus-config"
] }
zbus = { version = "3.14", default-features = false, features = ["tokio"] }

View file

@ -18,14 +18,11 @@ tracing-subscriber.workspace = true
tracing-log.workspace = true
tracing.workspace = true
nix = "0.26"
shlex = "1.3.0"
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
tokio = { version = "1.17.0", features = ["sync", "rt", "rt-multi-thread", "macros", "process"] }
itertools = "*"
freedesktop-desktop-entry = "0.5.0"
freedesktop-icons = "0.2.4"
i18n-embed = { version = "0.13", features = ["fluent-system", "desktop-requester"] }
i18n-embed-fl = "0.6"
rust-embed = "6.3"

View file

@ -12,6 +12,7 @@ use cctk::toplevel_info::ToplevelInfo;
use cctk::wayland_client::protocol::wl_data_device_manager::DndAction;
use cctk::wayland_client::protocol::wl_seat::WlSeat;
use cosmic::cosmic_config::{self, Config, CosmicConfigEntry};
use cosmic::desktop::{load_applications_for_app_ids, DesktopEntryData};
use cosmic::iced;
use cosmic::iced::event::listen_with;
use cosmic::iced::wayland::actions::data_device::DataFromMimeType;
@ -24,7 +25,6 @@ use cosmic::iced::widget::vertical_space;
use cosmic::iced::widget::{column, dnd_source, mouse_area, row, Column, Row};
use cosmic::iced::Color;
use cosmic::iced::{window, Subscription};
use cosmic::iced_core::window::Icon;
use cosmic::iced_runtime::core::alignment::Horizontal;
use cosmic::iced_runtime::core::event;
use cosmic::iced_sctk::commands::data_device::accept_mime_type;
@ -44,7 +44,6 @@ use cosmic::{
};
use cosmic::{Element, Theme};
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1;
use freedesktop_desktop_entry::DesktopEntry;
use futures::future::pending;
use iced::widget::container;
use iced::Alignment;
@ -52,9 +51,7 @@ use iced::Background;
use iced::Length;
use itertools::Itertools;
use rand::{thread_rng, Rng};
use std::borrow::Cow;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
@ -71,14 +68,14 @@ pub fn run() -> cosmic::iced::Result {
struct DockItem {
id: u32,
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo)>,
desktop_info: DesktopInfo,
desktop_info: DesktopEntryData,
}
impl DataFromMimeType for DockItem {
fn from_mime_type(&self, mime_type: &str) -> Option<Vec<u8>> {
if mime_type == MIME_TYPE {
if mime_type == MIME_TYPE && self.desktop_info.path.is_some() {
Some(
Url::from_file_path(self.desktop_info.path.clone())
Url::from_file_path(self.desktop_info.path.as_deref().unwrap())
.ok()?
.to_string()
.as_bytes()
@ -94,7 +91,7 @@ impl DockItem {
fn new(
id: u32,
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo)>,
desktop_info: DesktopInfo,
desktop_info: DesktopEntryData,
) -> Self {
Self {
id,
@ -167,11 +164,11 @@ impl DockItem {
dnd_source(
mouse_area(
icon_button
.on_press(
.on_press_maybe(
toplevels
.first()
.map(|t| Message::Activate(t.0.clone()))
.unwrap_or_else(|| Message::Exec(desktop_info.exec.clone())),
.or_else(|| desktop_info.exec.clone().map(Message::Exec)),
)
.width(Length::Shrink)
.height(Length::Shrink),
@ -202,7 +199,7 @@ struct DndOffer {
#[derive(Clone, Default)]
struct CosmicAppList {
core: cosmic::app::Core,
popup: Option<(window::Id, DockItem)>,
popup: Option<(window::Id, u32)>,
subscription_ctr: u32,
item_ctr: u32,
active_list: Vec<DockItem>,
@ -246,109 +243,6 @@ enum Message {
ConfigUpdated(AppListConfig),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum IconSource {
Name(String),
Path(PathBuf),
}
impl IconSource {
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())
}
}
fn as_cosmic_icon(&self) -> cosmic::widget::icon::Icon {
match self {
Self::Name(name) => cosmic::widget::icon::from_name(name.as_str())
.size(128)
.fallback(Some(cosmic::widget::icon::IconFallback::Names(vec![
"application-default".into(),
"application-x-executable".into(),
])))
.into(),
Self::Path(path) => cosmic::widget::icon(cosmic::widget::icon::from_path(path.clone())),
}
}
}
impl Default for IconSource {
fn default() -> Self {
Self::Name("application-default".to_string())
}
}
#[derive(Debug, Clone, Default)]
struct DesktopInfo {
id: String,
wm_class: Option<String>,
icon: IconSource,
exec: String,
name: String,
path: PathBuf,
}
fn desktop_info_for_app_ids(mut app_ids: Vec<String>) -> Vec<DesktopInfo> {
let app_ids_clone = app_ids.clone();
let mut ret = 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 let Some(i) = app_ids.iter().position(|s| {
s == de.appid || s.eq(&de.startup_wm_class().unwrap_or_default())
}) {
// 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())
};
app_ids.remove(i);
Some(DesktopInfo {
id: de.appid.to_string(),
wm_class: de.startup_wm_class().map(ToString::to_string),
icon,
exec: de.exec().unwrap_or_default().to_string(),
name: de.name(None).unwrap_or_default().to_string(),
path: path.clone(),
})
} else {
None
}
})
})
})
.collect_vec();
ret.append(
&mut app_ids
.into_iter()
.map(|id| DesktopInfo {
id,
icon: IconSource::default(),
..Default::default()
})
.collect_vec(),
);
ret.sort_by(|a, b| {
app_ids_clone
.iter()
.position(|id| id == &a.id || Some(id) == a.wm_class.as_ref())
.cmp(
&app_ids_clone
.iter()
.position(|id| id == &b.id || Some(id) == b.wm_class.as_ref()),
)
});
ret
}
fn index_in_list(
mut list_len: usize,
item_size: f32,
@ -404,15 +298,19 @@ impl cosmic::Application for CosmicAppList {
.unwrap_or_default();
let mut self_ = Self {
core,
favorite_list: desktop_info_for_app_ids(config.favorites.clone())
.into_iter()
.enumerate()
.map(|(favorite_ctr, e)| DockItem {
id: favorite_ctr as u32,
toplevels: Default::default(),
desktop_info: e,
})
.collect(),
favorite_list: load_applications_for_app_ids(
None,
config.favorites.iter().map(|s| &**s),
true,
)
.into_iter()
.enumerate()
.map(|(favorite_ctr, e)| DockItem {
id: favorite_ctr as u32,
toplevels: Default::default(),
desktop_info: e,
})
.collect(),
config,
..Default::default()
};
@ -450,7 +348,7 @@ impl cosmic::Application for CosmicAppList {
};
let new_id = window::Id::unique();
self.popup = Some((new_id, toplevel_group.clone()));
self.popup = Some((new_id, toplevel_group.id.clone()));
let mut popup_settings = self.core.applet.get_popup_settings(
window::Id::MAIN,
@ -511,12 +409,12 @@ impl cosmic::Application for CosmicAppList {
}
}
Message::Activate(handle) => {
if let Some(p) = self.popup.take() {
return destroy_popup(p.0);
}
if let Some(tx) = self.wayland_sender.as_ref() {
let _ = tx.send(WaylandRequest::Toplevel(ToplevelRequest::Activate(handle)));
}
if let Some(p) = self.popup.take() {
return destroy_popup(p.0);
}
}
Message::Quit(id) => {
if let Some(toplevel_group) = self
@ -649,18 +547,7 @@ impl cosmic::Application for CosmicAppList {
}
Message::DndData(file_path) => {
if let Some(DndOffer { dock_item, .. }) = self.dnd_offer.as_mut() {
if let Some(di) = std::fs::read_to_string(&file_path).ok().and_then(|input| {
DesktopEntry::decode(&file_path, &input)
.ok()
.map(|de| DesktopInfo {
id: de.id().to_string(),
wm_class: de.startup_wm_class().map(ToString::to_string),
icon: IconSource::from_unknown(de.icon().unwrap_or(de.appid)),
exec: de.exec().unwrap_or_default().to_string(),
name: de.name(None).unwrap_or_default().to_string(),
path: file_path.clone(),
})
}) {
if let Some(di) = cosmic::desktop::load_desktop_file(None, &file_path) {
self.item_ctr += 1;
*dock_item = Some(DockItem::new(self.item_ctr, Vec::new(), di));
}
@ -758,17 +645,14 @@ impl cosmic::Application for CosmicAppList {
info.app_id = format!("Unknown Application {}", self.item_ctr);
}
self.item_ctr += 1;
let desktop_info =
desktop_info_for_app_ids(vec![info.app_id.clone()])
.pop()
.unwrap_or_else(|| DesktopInfo {
id: info.app_id.clone(),
wm_class: None,
icon: IconSource::default(),
exec: String::new(),
name: info.app_id.clone(),
path: PathBuf::new(),
});
let desktop_info = load_applications_for_app_ids(
None,
std::iter::once(&*info.app_id),
true,
)
.into_iter()
.next()
.unwrap();
self.active_list.push(DockItem {
id: self.item_ctr,
toplevels: vec![(handle, info)],
@ -806,23 +690,13 @@ impl cosmic::Application for CosmicAppList {
}
},
WaylandUpdate::ActivationToken { token, exec } => {
let mut exec = shlex::Shlex::new(&exec);
let mut cmd = match exec.next() {
Some(cmd) if !cmd.contains('=') => std::process::Command::new(cmd),
_ => return Command::none(),
};
for arg in exec {
// TODO handle "%" args here if necessary?
if !arg.starts_with('%') {
cmd.arg(arg);
}
}
let mut envs = Vec::new();
if let Some(token) = token {
cmd.env("XDG_ACTIVATION_TOKEN", &token);
cmd.env("DESKTOP_STARTUP_ID", &token);
envs.push(("XDG_ACTIVATION_TOKEN", token.clone()));
envs.push(("DESKTOP_STARTUP_ID", token));
}
tokio::task::spawn_blocking(|| {
crate::process::spawn(cmd);
cosmic::desktop::spawn_desktop_exec(exec, envs);
});
}
}
@ -872,25 +746,29 @@ impl cosmic::Application for CosmicAppList {
}
// pull back configured items into the favorites list
self.favorite_list = desktop_info_for_app_ids(self.config.favorites.clone())
.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,
}
self.favorite_list = load_applications_for_app_ids(
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();
}
})
.collect();
}
Message::CloseRequested(id) => {
if Some(id) == self.popup.as_ref().map(|p| p.0) {
@ -1051,15 +929,20 @@ impl cosmic::Application for CosmicAppList {
.as_cosmic_icon()
.size(self.core.applet.suggested_size().0)
.into()
} else if let Some((
_popup_id,
DockItem {
} else if let Some((_popup_id, id)) = self.popup.as_ref().filter(|p| id == p.0) {
let Some(DockItem {
toplevels,
desktop_info,
..
},
)) = self.popup.as_ref().filter(|p| id == p.0)
{
}) = self
.favorite_list
.iter()
.chain(self.active_list.iter())
.find(|i| i.id == *id)
else {
return iced::widget::text("").into();
};
let is_favorite = self.config.favorites.contains(&desktop_info.id)
|| desktop_info
.wm_class
@ -1068,13 +951,20 @@ impl cosmic::Application for CosmicAppList {
let mut content = column![
iced::widget::text(&desktop_info.name).horizontal_alignment(Horizontal::Center),
cosmic::widget::button(iced::widget::text(fl!("new-window")))
.style(Button::Text)
.on_press(Message::Exec(desktop_info.exec.clone())),
]
.padding(8)
.spacing(4)
.align_items(Alignment::Center);
if let Some(exec) = desktop_info.exec.clone() {
content = content.push(
cosmic::widget::button(iced::widget::text(fl!("new-window")))
.style(Button::Text)
.on_press(Message::Exec(exec)),
);
content = content.push(divider::horizontal::default());
}
if !toplevels.is_empty() {
let mut list_col = column![];
for (handle, info) in toplevels {
@ -1089,10 +979,9 @@ impl cosmic::Application for CosmicAppList {
.on_press(Message::Activate(handle.clone())),
);
}
content = content.push(divider::horizontal::default());
content = content.push(list_col);
content = content.push(divider::horizontal::default());
}
content = content.push(divider::horizontal::default());
content = content.push(if is_favorite {
cosmic::widget::button(iced::widget::text(fl!("unfavorite")))
.style(Button::Text)

View file

@ -2,7 +2,6 @@
mod app;
mod config;
mod localize;
mod process;
mod wayland_handler;
mod wayland_subscription;

View file

@ -1,31 +0,0 @@
use std::process::{exit, Command, Stdio};
use nix::sys::wait::waitpid;
use nix::unistd::{fork, ForkResult};
/// Performs a double fork with setsid to spawn and detach a command.
pub fn spawn(mut command: Command) {
command
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
unsafe {
match fork() {
Ok(ForkResult::Parent { child }) => {
let _res = waitpid(Some(child), None);
}
Ok(ForkResult::Child) => {
let _res = nix::unistd::setsid();
let _res = command.spawn();
exit(0);
}
Err(why) => {
println!("failed to fork and spawn command: {}", why.desc());
}
}
}
}