Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show desktop entry actions in right click menu #643

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ pub enum Action {
EditHistory,
EditLocation,
EmptyTrash,
ExecEntryAction(usize),
ExtractHere,
Gallery,
HistoryNext,
Expand Down Expand Up @@ -158,6 +159,9 @@ impl Action {
}
Action::EmptyTrash => Message::TabMessage(None, tab::Message::EmptyTrash),
Action::ExtractHere => Message::ExtractHere(entity_opt),
Action::ExecEntryAction(action) => {
Message::TabMessage(entity_opt, tab::Message::ExecEntryAction(None, *action))
}
Action::Gallery => Message::TabMessage(entity_opt, tab::Message::GalleryToggle),
Action::HistoryNext => Message::TabMessage(entity_opt, tab::Message::GoNext),
Action::HistoryPrevious => Message::TabMessage(entity_opt, tab::Message::GoPrevious),
Expand Down Expand Up @@ -614,6 +618,30 @@ impl App {
}
}

fn exec_entry_action(entry: cosmic::desktop::DesktopEntryData, action: usize) {
if let Some(action) = entry.desktop_actions.get(action) {
// Largely copied from COSMIC app library
let mut exec = shlex::Shlex::new(&action.exec);
match exec.next() {
Some(cmd) if !cmd.contains('=') => {
let mut proc = tokio::process::Command::new(cmd);
for arg in exec {
if !arg.starts_with('%') {
proc.arg(arg);
}
}
let _ = proc.spawn();
}
_ => (),
}
} else {
log::warn!(
"Invalid actions index `{action}` for desktop entry {}",
entry.name
);
}
}

fn open_tab_entity(
&mut self,
location: Location,
Expand Down Expand Up @@ -2639,6 +2667,9 @@ impl Application for App {
tab::Command::EmptyTrash => {
self.dialog_pages.push_back(DialogPage::EmptyTrash);
}
tab::Command::ExecEntryAction(entry, action) => {
App::exec_entry_action(entry, action);
}
tab::Command::Iced(iced_command) => {
commands.push(iced_command.0.map(move |tab_message| {
message::app(Message::TabMessage(Some(entity), tab_message))
Expand Down
36 changes: 34 additions & 2 deletions src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use cosmic::{
},
Element,
};
use i18n_embed::LanguageLoader;
use mime_guess::Mime;
use std::collections::HashMap;

Expand Down Expand Up @@ -86,6 +87,7 @@ pub fn context_menu<'a>(
let mut selected_dir = 0;
let mut selected = 0;
let mut selected_trash_only = false;
let mut selected_desktop_entry = None;
let mut selected_types: Vec<Mime> = vec![];
tab.items_opt().map(|items| {
for item in items.iter() {
Expand All @@ -94,8 +96,16 @@ pub fn context_menu<'a>(
if item.metadata.is_dir() {
selected_dir += 1;
}
if item.location_opt == Some(Location::Trash) {
selected_trash_only = true;
match &item.location_opt {
Some(Location::Trash) => selected_trash_only = true,
Some(Location::Path(path)) => {
if selected == 1
&& path.extension().and_then(|s| s.to_str()) == Some("desktop")
{
selected_desktop_entry = Some(&**path);
}
}
_ => (),
}
selected_types.push(item.mime.clone());
}
Expand All @@ -104,6 +114,17 @@ pub fn context_menu<'a>(
selected_types.sort_unstable();
selected_types.dedup();
selected_trash_only = selected_trash_only && selected == 1;
// Parse the desktop entry if it is the only selection
let selected_desktop_entry = selected_desktop_entry.and_then(|path| {
if selected == 1 {
let lang_id = crate::localize::LANGUAGE_LOADER.current_language();
let language = lang_id.language.as_str();
// Cache?
cosmic::desktop::load_desktop_file(Some(language), path)
} else {
None
}
});

let mut children: Vec<Element<_>> = Vec::new();
match (&tab.mode, &tab.location) {
Expand All @@ -116,6 +137,17 @@ pub fn context_menu<'a>(
if tab::trash_entries() > 0 {
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into());
}
} else if let Some(entry) = selected_desktop_entry {
children.push(menu_item(fl!("open"), Action::Open).into());
for (i, action) in entry.desktop_actions.into_iter().enumerate() {
children.push(menu_item(action.name, Action::ExecEntryAction(i)).into())
}
children.push(divider::horizontal::light().into());
children.push(menu_item(fl!("rename"), Action::Rename).into());
children.push(menu_item(fl!("cut"), Action::Cut).into());
children.push(menu_item(fl!("copy"), Action::Copy).into());
// Should this simply bypass trash and remove the shortcut?
children.push(menu_item(fl!("move-to-trash"), Action::MoveToTrash).into());
} else if selected > 0 {
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
children.push(menu_item(fl!("open"), Action::Open).into());
Expand Down
21 changes: 21 additions & 0 deletions src/tab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use cosmic::{
};

use chrono::{DateTime, Utc};
use i18n_embed::LanguageLoader;
use mime_guess::{mime, Mime};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -1005,6 +1006,7 @@ pub enum Command {
ChangeLocation(String, Location, Option<PathBuf>),
DropFiles(PathBuf, ClipboardPaste),
EmptyTrash,
ExecEntryAction(cosmic::desktop::DesktopEntryData, usize),
Iced(TaskWrapper),
MoveToTrash(Vec<PathBuf>),
OpenFile(PathBuf),
Expand Down Expand Up @@ -1034,6 +1036,7 @@ pub enum Message {
EditLocationEnable,
OpenInNewTab(PathBuf),
EmptyTrash,
ExecEntryAction(Option<PathBuf>, usize),
Gallery(bool),
GalleryPrevious,
GalleryNext,
Expand Down Expand Up @@ -2312,6 +2315,24 @@ impl Tab {
Message::EmptyTrash => {
commands.push(Command::EmptyTrash);
}
Message::ExecEntryAction(path, action) => {
let lang_id = crate::localize::LANGUAGE_LOADER.current_language();
let language = lang_id.language.as_str();
match path.map_or_else(
|| {
let items = self.items_opt.as_deref()?;
items.iter().find(|item| item.selected).and_then(|item| {
let location = item.location_opt.as_ref()?;
let path = location.path_opt()?;
cosmic::desktop::load_desktop_file(Some(language), path)
})
},
|path| cosmic::desktop::load_desktop_file(Some(language), path),
) {
Some(entry) => commands.push(Command::ExecEntryAction(entry, action)),
None => log::warn!("Invalid desktop entry path passed to ExecEntryAction"),
}
}
Message::Gallery(gallery) => {
self.gallery = gallery;
}
Expand Down