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", "shlex",
"smol_str", "smol_str",
"tokio", "tokio",
"url",
"vergen", "vergen",
] ]

View file

@ -32,6 +32,7 @@ i18n-embed-fl = "0.7"
icu_collator = "1.5" icu_collator = "1.5"
icu_provider = { version = "1.5", features = ["sync"] } icu_provider = { version = "1.5", features = ["sync"] }
rust-embed = "8" rust-embed = "8"
url = "2.5"
[dependencies.cosmic-files] [dependencies.cosmic-files]
git = "https://github.com/pop-os/cosmic-files.git" 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 // SPDX-License-Identifier: GPL-3.0-only
use alacritty_terminal::{event::Event as TermEvent, term, term::color::Colors as TermColors, tty}; 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::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::DndDestination;
use cosmic::Apply;
use cosmic::{ use cosmic::{
app::{message, Command, Core, Settings}, app::{command, message, Command, Core, Settings},
cosmic_config::{self, ConfigSet, CosmicConfigEntry}, cosmic_config::{self, ConfigSet, CosmicConfigEntry},
cosmic_theme, executor, cosmic_theme, executor,
iced::{ iced::{
@ -56,10 +59,14 @@ use terminal::{Terminal, TerminalPaneGrid, TerminalScroll};
mod terminal; mod terminal;
use terminal_box::terminal_box; use terminal_box::terminal_box;
use crate::dnd::DndDrop;
mod terminal_box; mod terminal_box;
mod terminal_theme; mod terminal_theme;
mod dnd;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref ICON_CACHE: Mutex<IconCache> = Mutex::new(IconCache::new()); static ref ICON_CACHE: Mutex<IconCache> = Mutex::new(IconCache::new());
} }
@ -295,6 +302,7 @@ pub enum Message {
DefaultFontWeight(usize), DefaultFontWeight(usize),
DefaultZoomStep(usize), DefaultZoomStep(usize),
DialogMessage(DialogMessage), DialogMessage(DialogMessage),
Drop(Option<(pane_grid::Pane, segmented_button::Entity, DndDrop)>),
Find(bool), Find(bool),
FindNext, FindNext,
FindPrevious, FindPrevious,
@ -1894,6 +1902,16 @@ impl Application for App {
return dialog.update(dialog_message); 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) => { Message::Find(find) => {
self.find = find; self.find = find;
if find { if find {
@ -2673,7 +2691,19 @@ impl Application for App {
// TODO // 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) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)