Support drag-and-drop for files

Insert file paths when files are dropped in terminal from a file manager.
This commit is contained in:
Jason Rodney Hansen 2024-08-03 20:33:55 -06:00 committed by Jeremy Soller
parent fbb69fd399
commit 059dd5fa97
4 changed files with 86 additions and 3 deletions

1
Cargo.lock generated
View file

@ -1237,6 +1237,7 @@ dependencies = [
"shlex",
"smol_str",
"tokio",
"url",
"vergen",
]

View file

@ -32,6 +32,7 @@ i18n-embed-fl = "0.7"
icu_collator = "1.5"
icu_provider = { version = "1.5", features = ["sync"] }
rust-embed = "8"
url = "2.5"
[dependencies.cosmic-files]
git = "https://github.com/pop-os/cosmic-files.git"

51
src/dnd.rs Normal file
View file

@ -0,0 +1,51 @@
use cosmic::iced::clipboard::mime::AllowedMimeTypes;
use std::{borrow::Cow, error::Error, path::PathBuf, str};
use url::Url;
#[derive(Clone, Debug)]
pub struct DndDrop {
pub paths: Vec<PathBuf>,
}
impl AllowedMimeTypes for DndDrop {
fn allowed() -> Cow<'static, [String]> {
Cow::from(vec![
"x-special/gnome-copied-files".to_string(),
"text/uri-list".to_string(),
])
}
}
impl TryFrom<(Vec<u8>, String)> for DndDrop {
type Error = Box<dyn Error>;
fn try_from(value: (Vec<u8>, String)) -> Result<Self, Self::Error> {
let (data, mime) = value;
let mut paths = Vec::new();
match mime.as_str() {
"text/uri-list" => {
let text = str::from_utf8(&data)?;
for line in text.lines() {
let url = Url::parse(line)?;
match url.to_file_path() {
Ok(path) => paths.push(path),
Err(()) => Err(format!("invalid file URL {:?}", url))?,
}
}
}
"x-special/gnome-copied-files" => {
let text = str::from_utf8(&data)?;
for (i, line) in text.lines().enumerate() {
if i != 0 {
let url = Url::parse(line)?;
match url.to_file_path() {
Ok(path) => paths.push(path),
Err(()) => Err(format!("invalid file URL {:?}", url))?,
}
}
}
}
_ => Err(format!("unsupported mime type {:?}", mime))?,
}
Ok(Self { paths })
}
}

View file

@ -2,10 +2,13 @@
// SPDX-License-Identifier: GPL-3.0-only
use alacritty_terminal::{event::Event as TermEvent, term, term::color::Colors as TermColors, tty};
use cosmic::iced::clipboard::dnd::DndAction;
use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::DndDestination;
use cosmic::Apply;
use cosmic::{
app::{message, Command, Core, Settings},
app::{command, message, Command, Core, Settings},
cosmic_config::{self, ConfigSet, CosmicConfigEntry},
cosmic_theme, executor,
iced::{
@ -56,10 +59,14 @@ use terminal::{Terminal, TerminalPaneGrid, TerminalScroll};
mod terminal;
use terminal_box::terminal_box;
use crate::dnd::DndDrop;
mod terminal_box;
mod terminal_theme;
mod dnd;
lazy_static::lazy_static! {
static ref ICON_CACHE: Mutex<IconCache> = Mutex::new(IconCache::new());
}
@ -97,7 +104,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
shell_args.push(arg);
}
}
#[cfg(all(unix, not(target_os = "redox")))]
if daemonize {
match fork::daemon(true, true) {
@ -295,6 +302,7 @@ pub enum Message {
DefaultFontWeight(usize),
DefaultZoomStep(usize),
DialogMessage(DialogMessage),
Drop(Option<(pane_grid::Pane, segmented_button::Entity, DndDrop)>),
Find(bool),
FindNext,
FindPrevious,
@ -1894,6 +1902,16 @@ impl Application for App {
return dialog.update(dialog_message);
}
}
Message::Drop(Some((pane, entity, data))) => {
self.pane_model.focus = pane;
if let Ok(value) = shlex::try_join(data.paths.iter().filter_map(|p| p.to_str())) {
return Command::batch([
self.update_focus(),
command::message::app(Message::PasteValue(Some(entity), value)),
]);
}
}
Message::Drop(None) => {}
Message::Find(find) => {
self.find = find;
if find {
@ -2673,7 +2691,19 @@ impl Application for App {
// TODO
}
pane_grid::Content::new(tab_column)
DndDestination::for_data::<DndDrop>(tab_column, move |data, action| {
if let Some(data) = data {
if action == DndAction::Move {
Message::Drop(Some((pane, entity, data)))
} else {
log::warn!("unsuppported action: {:?}", action);
Message::Drop(None)
}
} else {
Message::Drop(None)
}
})
.apply(pane_grid::Content::new)
})
.width(Length::Fill)
.height(Length::Fill)