diff --git a/Cargo.lock b/Cargo.lock index 5f7beb49..8c753940 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4744,6 +4744,7 @@ dependencies = [ "inventory", "libc", "mlua", + "parking_lot", "paste", "percent-encoding", "ratatui-core", diff --git a/yazi-actor/Cargo.toml b/yazi-actor/Cargo.toml index e4daff42..6d256e8c 100644 --- a/yazi-actor/Cargo.toml +++ b/yazi-actor/Cargo.toml @@ -47,6 +47,7 @@ hashbrown = { workspace = true } indexmap = { workspace = true } inventory = { workspace = true } mlua = { workspace = true } +parking_lot = { workspace = true } paste = { workspace = true } ratatui-core = { workspace = true } scopeguard = { workspace = true } diff --git a/yazi-actor/src/app/clipboard.rs b/yazi-actor/src/app/clipboard.rs index 56b2f8ae..a855b45e 100644 --- a/yazi-actor/src/app/clipboard.rs +++ b/yazi-actor/src/app/clipboard.rs @@ -25,13 +25,7 @@ impl Actor for Clipboard { runtime_scope!(LUA, "root", { let root = LUA.globals().raw_get::("Root")?.call_method::
("new", area)?; - if form.event.is_paste_offer() { - root.call_method::<()>("paste_offer", form.event)?; - } else if form.event.is_read() { - root.call_method::<()>("paste_data", form.event)?; - } else { - root.call_method::<()>("write_result", form.event)?; - } + root.call_method::<()>("clipboard", form.event)?; Ok(()) }) diff --git a/yazi-actor/src/app/mod.rs b/yazi-actor/src/app/mod.rs index c95c54c2..0e5d148e 100644 --- a/yazi-actor/src/app/mod.rs +++ b/yazi-actor/src/app/mod.rs @@ -1,6 +1,7 @@ yazi_macro::mod_flat!( accept_payload bootstrap + clipboard deprecate dnd focus @@ -16,5 +17,4 @@ yazi_macro::mod_flat!( theme title update_progress - clipboard ); diff --git a/yazi-actor/src/mgr/copy.rs b/yazi-actor/src/mgr/copy.rs index b6ecdc54..240caf8a 100644 --- a/yazi-actor/src/mgr/copy.rs +++ b/yazi-actor/src/mgr/copy.rs @@ -70,16 +70,9 @@ impl Actor for Copy { "uri_list" => { data.push(ClipboardData { mime: b"text/uri-list".to_vec(), - payload: s.clone(), + payload: s, alias: b"text/plain".to_vec(), }); - #[cfg(target_os = "linux")] - // Because Thunar (and likely others) won't reconize `text/uri-list` - data.push(ClipboardData { - mime: b"x-special/gnome-copied-files".to_vec(), - payload: [b"copy\n".to_vec(), s].concat(), - alias: vec![], - }); } _ => { data.push(ClipboardData { mime: b"text/plain".to_vec(), payload: s, alias: vec![] }); diff --git a/yazi-actor/src/mgr/paste.rs b/yazi-actor/src/mgr/paste.rs index 3a1c2ceb..3d3032fc 100644 --- a/yazi-actor/src/mgr/paste.rs +++ b/yazi-actor/src/mgr/paste.rs @@ -23,7 +23,7 @@ impl Actor for Paste { mgr.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(mgr.yanked.urls())); act!(mgr:unyank, cx) } else { - succ!(cx.core.tasks.file_copy(&mgr.yanked, dest, form.force, Some(form.follow))); + succ!(cx.core.tasks.file_copy(&mgr.yanked, dest, form.force)); } } } diff --git a/yazi-cli/src/env/env.rs b/yazi-cli/src/env/env.rs index 21c06fbb..1cc8e981 100644 --- a/yazi-cli/src/env/env.rs +++ b/yazi-cli/src/env/env.rs @@ -112,7 +112,6 @@ impl Env { writeln!(s, " wl-copy/paste: {} / {}", Self::dep_version("wl-copy", "--version"), Self::dep_version("wl-paste", "--version"))?; writeln!(s, " xclip : {}", Self::dep_version("xclip", "-version"))?; writeln!(s, " xsel : {}", Self::dep_version("xsel", "--version"))?; - writeln!(s, " OSC 5522 : {:?}", yazi_emulator::EMULATOR.osc_5522)?; writeln!(s, "\nRoutine")?; writeln!(s, " `file -bL --mime-type`: {}", Self::file1_output())?; diff --git a/yazi-core/src/tasks/file.rs b/yazi-core/src/tasks/file.rs index 276ad051..52ab64b8 100644 --- a/yazi-core/src/tasks/file.rs +++ b/yazi-core/src/tasks/file.rs @@ -22,7 +22,7 @@ impl Tasks { } } - pub fn file_copy(&self, src: &Yanked, dest: &UrlBuf, force: bool, follow: Option) { + pub fn file_copy(&self, src: &Yanked, dest: &UrlBuf, force: bool) { self.scheduler.behavior.reset(); for u in src.urls() { @@ -33,7 +33,7 @@ impl Tasks { if force && u == to { debug!("file_copy: same file, skip {to:?}"); } else { - self.scheduler.file_copy(FileInCopy::new(u.clone(), to, force, follow)); + self.scheduler.file_copy(FileInCopy::new(u.clone(), to, force)); } } } diff --git a/yazi-emulator/src/emulator.rs b/yazi-emulator/src/emulator.rs index 72e5f5e9..66169346 100644 --- a/yazi-emulator/src/emulator.rs +++ b/yazi-emulator/src/emulator.rs @@ -69,16 +69,13 @@ impl Emulator { let csi_16t = Self::csi_16t(&resp).unwrap_or_default(); - let osc_5522 = - ["\x1b[?5522;1$y", "\x1b[?5522;2$y", "\x1b[?5522;3$y"].iter().any(|s| resp.contains(s)); - Ok(Self { kind, version: Self::csi_gt_q(&resp).unwrap_or_default(), light: Self::light_bg(&resp).unwrap_or_default(), csi_16t, force_16t: Self::force_16t(csi_16t), - osc_5522, + osc_5522: Self::osc_5522(&resp), }) } @@ -194,6 +191,10 @@ impl Emulator { } } + fn osc_5522(resp: &str) -> bool { + ["\x1b[?5522;1$y", "\x1b[?5522;2$y", "\x1b[?5522;3$y"].iter().any(|s| resp.contains(s)) + } + fn force_16t((w, h): (u16, u16)) -> bool { if w == 0 || h == 0 { return false; diff --git a/yazi-fm/src/dispatcher.rs b/yazi-fm/src/dispatcher.rs index 96e742fb..c02ffe98 100644 --- a/yazi-fm/src/dispatcher.rs +++ b/yazi-fm/src/dispatcher.rs @@ -104,14 +104,13 @@ impl<'a> Dispatcher<'a> { } fn dispatch_clipboard(&mut self, clip: ClipboardEvent) -> Result<()> { - if self.app.core.input.main.visible && clip.is_read() { + if self.app.core.input.main.visible { if let Some(text) = clip.text() { self.dispatch_paste(text)?; + return Ok(()); } - Ok(()) - } else { - let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term); - act!(app:clipboard, cx, clip).map(|_| ()) } + let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term); + act!(app:clipboard, cx, clip).map(|_| ()) } } diff --git a/yazi-parser/src/app/mod.rs b/yazi-parser/src/app/mod.rs index a1adb43d..a7124819 100644 --- a/yazi-parser/src/app/mod.rs +++ b/yazi-parser/src/app/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(deprecate dnd lua mouse plugin quit reflow title update_progress clipboard); +yazi_macro::mod_flat!(clipboard deprecate dnd lua mouse plugin quit reflow title update_progress); diff --git a/yazi-parser/src/spark/spark.rs b/yazi-parser/src/spark/spark.rs index 5c1bb13a..8d6b261e 100644 --- a/yazi-parser/src/spark/spark.rs +++ b/yazi-parser/src/spark/spark.rs @@ -10,6 +10,7 @@ pub enum Spark<'a> { // App AppAcceptPayload(yazi_dds::Payload<'a>), AppBootstrap(crate::VoidForm), + AppClipboard(crate::app::ClipboardForm), AppDeprecate(crate::app::DeprecateForm), AppDnd(crate::app::DndForm), AppFocus(crate::VoidForm), @@ -25,7 +26,6 @@ pub enum Spark<'a> { AppTheme(crate::VoidForm), AppTitle(crate::app::TitleForm), AppUpdateProgress(crate::app::UpdateProgressForm), - AppClipboard(crate::app::ClipboardForm), // Mgr Arrow(crate::ArrowForm), @@ -206,6 +206,7 @@ impl<'a> IntoLua for Spark<'a> { // App Self::AppAcceptPayload(b) => b.into_lua(lua), Self::AppBootstrap(b) => b.into_lua(lua), + Self::AppClipboard(b) => b.into_lua(lua), Self::AppDeprecate(b) => b.into_lua(lua), Self::AppDnd(b) => b.into_lua(lua), Self::AppFocus(b) => b.into_lua(lua), @@ -221,7 +222,6 @@ impl<'a> IntoLua for Spark<'a> { Self::AppTheme(b) => b.into_lua(lua), Self::AppTitle(b) => b.into_lua(lua), Self::AppUpdateProgress(b) => b.into_lua(lua), - Self::AppClipboard(b) => b.into_lua(lua), // Mgr Self::Arrow(b) => b.into_lua(lua), @@ -388,6 +388,7 @@ try_from_spark!( // App try_from_spark!(crate::ArrowForm, mgr:arrow, mgr:tab_swap); +try_from_spark!(crate::app::ClipboardForm, app:clipboard); try_from_spark!(crate::app::DeprecateForm, app:deprecate); try_from_spark!(crate::app::DndForm, app:dnd); try_from_spark!(crate::app::LuaForm, app:lua); @@ -397,7 +398,6 @@ try_from_spark!(crate::app::QuitForm, app:quit, mgr:quit); try_from_spark!(crate::app::ReflowForm, app:reflow, app:resize, app:resume); try_from_spark!(crate::app::TitleForm, app:title); try_from_spark!(crate::app::UpdateProgressForm, app:update_progress); -try_from_spark!(crate::app::ClipboardForm, app:clipboard); try_from_spark!(crate::cmp::CloseForm, cmp:close); try_from_spark!(crate::cmp::ShowForm, cmp:show); try_from_spark!(crate::cmp::TriggerForm, cmp:trigger); diff --git a/yazi-plugin/preset/components/root.lua b/yazi-plugin/preset/components/root.lua index 01453468..a8718448 100644 --- a/yazi-plugin/preset/components/root.lua +++ b/yazi-plugin/preset/components/root.lua @@ -100,30 +100,15 @@ function Root:drop(event) end end --- Clipboard events -function Root:paste_offer(event) - if event and event.pw then +function Root:clipboard(event) + if event and event.type == "mimetypes" and event.pw then -- No harm in asking for unavailable types local mimetypes = "text/plain text/uri-list" - ya.dbg("Requesting ReadClipboard") rt.tty:queue("ReadClipboard", { mimes = mimetypes, pw = event.pw, name = "Paste Event", primary = event.primary }) rt.tty:flush() + elseif event and event.type == "data" then + if event.data["text/uri-list"] ~= nil then + require("clipboard").copy_uri_list(event.data["text/uri-list"]) + end end end - -function Root:paste_data(event) - if event.data["text/uri-list"] ~= nil then - local list = event.data["text/uri-list"] - ya.dbg("Pasting URI list:", list) - require("clipboard").copy_uri_list(list) - end - -- TODO !!5522!! Suport non text formats - -- if event.data["image/png"] ~= nil then - -- local type = "image/png" - -- local data = event.data["image/png"] - -- ya.dbg("Pasting image/png:") - -- require("clipboard").paste_image(type, data) - -- end -end - -function Root:write_result(event) end diff --git a/yazi-plugin/src/runtime/term.rs b/yazi-plugin/src/runtime/term.rs index e07ba439..06e47e9d 100644 --- a/yazi-plugin/src/runtime/term.rs +++ b/yazi-plugin/src/runtime/term.rs @@ -7,7 +7,6 @@ pub(super) fn term() -> Composer { match key { b"light" => EMULATOR.light.into_lua(lua), b"cell_size" => cell_size(lua)?.into_lua(lua), - b"osc5522_clipboard" => EMULATOR.osc_5522.into_lua(lua), _ => Ok(Value::Nil), } } diff --git a/yazi-scheduler/src/file/in.rs b/yazi-scheduler/src/file/in.rs index 792a9f64..613d701e 100644 --- a/yazi-scheduler/src/file/in.rs +++ b/yazi-scheduler/src/file/in.rs @@ -157,9 +157,9 @@ impl TaskIn for FileInCopy { } impl FileInCopy { - pub fn new(from: UrlBuf, to: UrlBuf, force: bool, follow: Option) -> Self { + pub fn new(from: UrlBuf, to: UrlBuf, force: bool) -> Self { Self { - follow: follow.unwrap_or(!from.auth().covariant(to.auth())), + follow: !from.auth().covariant(to.auth()), id: Id::ZERO, from, to, @@ -189,7 +189,7 @@ impl FromLua for FileInCopy { return Err("constructing FileInCopy from non-table value".into_lua_err()); }; - Ok(Self::new(t.raw_get("from")?, t.raw_get("to")?, t.raw_get("force")?, None)) + Ok(Self::new(t.raw_get("from")?, t.raw_get("to")?, t.raw_get("force")?)) } } // --- Cut diff --git a/yazi-term/src/event/clipboard.rs b/yazi-term/src/event/clipboard.rs index bd00f4a8..3d0d11a6 100644 --- a/yazi-term/src/event/clipboard.rs +++ b/yazi-term/src/event/clipboard.rs @@ -1,8 +1,6 @@ -use std::str::SplitWhitespace; - use strum::{FromRepr, IntoStaticStr}; -use crate::parser::{Osc5522Status, StateOsc5522}; +use crate::{event::mime::MimeList, parser::{Osc5522Status, StateOsc5522}}; #[derive(Clone, Debug, Eq, PartialEq)] pub enum ClipboardEvent { @@ -15,7 +13,7 @@ pub enum ClipboardEvent { #[derive(Clone, Debug, Eq, PartialEq)] pub struct ClipboardPaste { - pub mimes: ClipboardMimeList, + pub mimes: MimeList, pub primary: bool, pub pw: Vec, } @@ -28,7 +26,7 @@ pub struct ClipboardData { #[derive(Clone, Debug, Eq, PartialEq)] pub struct ClipboardRead { - pub mimes: ClipboardMimeList, + pub mimes: MimeList, pub primary: bool, pub data: Vec, } @@ -41,15 +39,15 @@ pub struct ClipboardError { impl ClipboardEvent { pub fn r#type(&self) -> &'static str { match self { - Self::ReadMimetypes(_) => "read_mimetypes", - Self::ReadData(_) => "read_data", - Self::ReadError(_) => "read_error", - Self::WriteSuccess => "write_success", - Self::WriteError(_) => "write_error", + Self::ReadMimetypes(_) => "mimetypes", + Self::ReadData(_) => "data", + Self::ReadError(_) => "error", + Self::WriteSuccess => "success", + Self::WriteError(_) => "error", } } - pub fn mimes(&self) -> Option<&ClipboardMimeList> { + pub fn mimes(&self) -> Option<&MimeList> { match self { Self::ReadMimetypes(e) => Some(&e.mimes), Self::ReadData(e) => Some(&e.mimes), @@ -80,16 +78,9 @@ impl ClipboardEvent { } } - pub fn is_paste_offer(&self) -> bool { - match self { - Self::ReadMimetypes(_) => true, - _ => false, - } - } - pub fn is_read(&self) -> bool { match self { - Self::ReadError(_) | Self::ReadData(_) => true, + Self::ReadMimetypes(_) | Self::ReadError(_) | Self::ReadData(_) => true, _ => false, } } @@ -100,7 +91,7 @@ impl ClipboardEvent { if mime.first()? == b"." => { ClipboardEvent::ReadMimetypes(ClipboardPaste { - mimes: ClipboardMimeList::new(s.payload.first()?.to_owned())?, + mimes: MimeList::new(s.payload.first()?.to_owned())?, primary: s.primary, pw: s.pw, }) @@ -114,7 +105,7 @@ impl ClipboardEvent { mimes.push(b' '); } ClipboardEvent::ReadData(ClipboardRead { - mimes: ClipboardMimeList::new(mimes)?, + mimes: MimeList::new(mimes)?, primary: s.primary, data, }) @@ -140,16 +131,6 @@ pub enum ClipboardType { Write = 2, } -// --- MIME list -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct ClipboardMimeList(String); - -impl ClipboardMimeList { - pub fn new(b: Vec) -> Option { Some(Self(String::from_utf8(b).ok()?)) } - - pub fn iter(&self) -> SplitWhitespace<'_> { self.0.split_whitespace() } -} - // --- Error payload parsing fn parse_error(status: Option) -> Option { match status { diff --git a/yazi-term/src/event/dnd.rs b/yazi-term/src/event/dnd.rs index d3c81b6a..e00870e2 100644 --- a/yazi-term/src/event/dnd.rs +++ b/yazi-term/src/event/dnd.rs @@ -1,10 +1,8 @@ -use std::str::SplitWhitespace; - use base64::Engine; use strum::{FromRepr, IntoStaticStr}; use yazi_shim::BASE64_SANE; -use crate::parser::StateOsc72; +use crate::{event::mime::MimeList, parser::StateOsc72}; #[derive(Clone, Debug, Eq, PartialEq)] pub enum DndEvent { @@ -63,7 +61,7 @@ pub struct DndDropEnter { pub x: u32, pub y: u32, pub op: DndOp, - pub mimes: DndMimeList, + pub mimes: MimeList, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -71,7 +69,7 @@ pub struct DndDropReady { pub x: u32, pub y: u32, pub op: DndOp, - pub mimes: DndMimeList, + pub mimes: MimeList, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -144,7 +142,7 @@ impl DndEvent { } } - pub fn mimes(&self) -> Option<&DndMimeList> { + pub fn mimes(&self) -> Option<&MimeList> { match self { Self::DropEnter(e) => Some(&e.mimes), Self::DropReady(e) => Some(&e.mimes), @@ -185,13 +183,13 @@ impl DndEvent { x: s.x?.try_into().ok()?, y: s.y?.try_into().ok()?, op: DndOp::from_repr(s.op?)?, - mimes: DndMimeList::new(s.payload)?, + mimes: MimeList::new(s.payload)?, }), b'M' => Self::DropReady(DndDropReady { x: s.x?.try_into().ok()?, y: s.y?.try_into().ok()?, op: DndOp::from_repr(s.op?)?, - mimes: DndMimeList::new(s.payload)?, + mimes: MimeList::new(s.payload)?, }), b'r' => Self::DropArrive(DndDropArrive { idx: s.x?.try_into().ok()?, @@ -217,14 +215,6 @@ pub enum DndOp { } // --- MIME list -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct DndMimeList(String); - -impl DndMimeList { - pub fn new(b: Vec) -> Option { Some(Self(String::from_utf8(b).ok()?)) } - - pub fn iter(&self) -> SplitWhitespace<'_> { self.0.split_whitespace() } -} // --- Error payload parsing fn parse_error(payload: Vec) -> Option<(String, String)> { diff --git a/yazi-term/src/event/lua.rs b/yazi-term/src/event/lua.rs index 2080bacb..d2edfc62 100644 --- a/yazi-term/src/event/lua.rs +++ b/yazi-term/src/event/lua.rs @@ -54,13 +54,12 @@ impl UserData for ClipboardEvent { fields.add_cached_field_mut("data", |lua, me| match me { Self::ReadData(ClipboardRead { data, .. }) => lua - .create_table_from(data.iter().map(|d| { - ( - lua.create_string(&d.mime).ok(), - // TODO !!5522!! is this the best way - lua.create_external_string(&*d.data).ok(), - ) - }))? + .create_table_from( + data + .iter() + .map(|d| Ok((lua.create_string(&d.mime)?, lua.create_external_string(&*d.data)?))) + .collect::, mlua::Error>>()?, + )? .into_lua(lua), _ => Ok(Value::Nil), }); diff --git a/yazi-term/src/event/mime.rs b/yazi-term/src/event/mime.rs new file mode 100644 index 00000000..b2c5e2ce --- /dev/null +++ b/yazi-term/src/event/mime.rs @@ -0,0 +1,10 @@ +use std::str::SplitWhitespace; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MimeList(String); + +impl MimeList { + pub fn new(b: Vec) -> Option { Some(Self(String::from_utf8(b).ok()?)) } + + pub fn iter(&self) -> SplitWhitespace<'_> { self.0.split_whitespace() } +} diff --git a/yazi-term/src/event/mod.rs b/yazi-term/src/event/mod.rs index a330dbd7..4acfab0b 100644 --- a/yazi-term/src/event/mod.rs +++ b/yazi-term/src/event/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(clipboard dnd event keyboard lua modifiers mouse); +yazi_macro::mod_flat!(clipboard dnd event keyboard lua mime modifiers mouse); diff --git a/yazi-tty/src/sequence/clipboard.rs b/yazi-tty/src/sequence/clipboard.rs index dfdaf4e6..2c9b0580 100644 --- a/yazi-tty/src/sequence/clipboard.rs +++ b/yazi-tty/src/sequence/clipboard.rs @@ -52,20 +52,17 @@ pub struct ReadClipboard<'a> { impl Display for ReadClipboard<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let b64_mime = BASE64_PAD.encode(self.mime).into_bytes(); - let mime_str = unsafe { String::from_utf8_unchecked(b64_mime) }; + let b64_mime = BASE64_PAD.encode(self.mime); let mut metadata = String::new(); if self.pw.len() > 0 { - let b64_pw = BASE64_PAD.encode(self.pw).into_bytes(); - let pw_str = unsafe { String::from_utf8_unchecked(b64_pw) }; - let b64_name = BASE64_PAD.encode(self.name).into_bytes(); - let name_str = unsafe { String::from_utf8_unchecked(b64_name) }; - metadata.push_str(&format!(":pw={}:name={}", pw_str, name_str)); + let b64_pw = BASE64_PAD.encode(self.pw); + let b64_name = BASE64_PAD.encode(self.name); + metadata.push_str(&format!(":pw={}:name={}", b64_pw, b64_name)); } if self.primary { metadata.push_str(":loc=primary"); } - write!(f, "\x1b]5522;type=read{};{}\x1b\\", metadata, mime_str) + write!(f, "\x1b]5522;type=read{};{}\x1b\\", metadata, b64_mime) } } @@ -91,20 +88,17 @@ impl Display for WriteClipboard<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "\x1b]5522;type=write\x1b\\")?; for item in &self.data { - let b64_mime = BASE64_PAD.encode(item.mime).into_bytes(); - let mime_str = unsafe { String::from_utf8_unchecked(b64_mime) }; + let b64_mime = BASE64_PAD.encode(item.mime); let data = item.payload; for (_, chunk) in data.chunks(4096).enumerate() { - let b64_chunk = BASE64_PAD.encode(chunk).into_bytes(); - let s = unsafe { String::from_utf8_unchecked(b64_chunk) }; - write!(f, "\x1b]5522;type=wdata:mime={};{s}\x1b\\", mime_str)?; + let b64_chunk = BASE64_PAD.encode(chunk); + write!(f, "\x1b]5522;type=wdata:mime={};{}\x1b\\", b64_mime, b64_chunk)?; } if item.alias.len() > 0 { - let b64_alias = BASE64_PAD.encode(item.alias).into_bytes(); - let s = unsafe { String::from_utf8_unchecked(b64_alias) }; - write!(f, "\x1b]5522;type=walias:mime={};{s}\x1b\\", mime_str)?; + let b64_alias = BASE64_PAD.encode(item.alias); + write!(f, "\x1b]5522;type=walias:mime={};{}\x1b\\", b64_mime, b64_alias)?; } } write!(f, "\x1b]5522;type=wdata\x1b\\")