diff --git a/Cargo.lock b/Cargo.lock index b6f11cb..188ea7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1237,6 +1237,7 @@ dependencies = [ "shlex", "smol_str", "tokio", + "url", "vergen", ] diff --git a/Cargo.toml b/Cargo.toml index c73efe2..ae7d5f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/dnd.rs b/src/dnd.rs new file mode 100644 index 0000000..a054343 --- /dev/null +++ b/src/dnd.rs @@ -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, +} + +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, String)> for DndDrop { + type Error = Box; + fn try_from(value: (Vec, String)) -> Result { + 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 }) + } +} diff --git a/src/main.rs b/src/main.rs index 62a265e..b0804c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 = Mutex::new(IconCache::new()); } @@ -97,7 +104,7 @@ fn main() -> Result<(), Box> { 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::(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)