diff --git a/wezterm-gui/src/termwindow/mod.rs b/wezterm-gui/src/termwindow/mod.rs index c92e5a27123..0ba025e422a 100644 --- a/wezterm-gui/src/termwindow/mod.rs +++ b/wezterm-gui/src/termwindow/mod.rs @@ -1011,6 +1011,28 @@ impl TermWindow { } Ok(true) } + WindowEvent::DroppedString(text) => { + let pane = match self.get_active_pane_or_overlay() { + Some(pane) => pane, + None => return Ok(true), + }; + pane.send_paste(text.as_str())?; + Ok(true) + } + WindowEvent::DroppedUrl(urls) => { + let pane = match self.get_active_pane_or_overlay() { + Some(pane) => pane, + None => return Ok(true), + }; + let urls = urls + .iter() + .map(|url| self.config.quote_dropped_files.escape(&url.to_string())) + .collect::>() + .join(" ") + + " "; + pane.send_paste(urls.as_str())?; + Ok(true) + } WindowEvent::DroppedFile(paths) => { let pane = match self.get_active_pane_or_overlay() { Some(pane) => pane, @@ -1024,7 +1046,8 @@ impl TermWindow { .escape(&path.to_string_lossy()) }) .collect::>() - .join(" "); + .join(" ") + + " "; pane.send_paste(&paths)?; Ok(true) } diff --git a/window/examples/async.rs b/window/examples/async.rs index a42b5040c6c..419e7a200ac 100644 --- a/window/examples/async.rs +++ b/window/examples/async.rs @@ -85,6 +85,8 @@ impl MyWindow { | WindowEvent::FocusChanged(_) | WindowEvent::DraggedFile(_) | WindowEvent::DroppedFile(_) + | WindowEvent::DroppedUrl(_) + | WindowEvent::DroppedString(_) | WindowEvent::PerformKeyAssignment(_) | WindowEvent::MouseLeave | WindowEvent::SetInnerSizeCompleted => {} diff --git a/window/src/lib.rs b/window/src/lib.rs index ac97cc1dcab..29c670b32de 100644 --- a/window/src/lib.rs +++ b/window/src/lib.rs @@ -7,6 +7,7 @@ use std::any::Any; use std::path::PathBuf; use std::rc::Rc; use thiserror::Error; +use url::Url; pub mod bitmaps; pub use wezterm_color_types as color; mod configuration; @@ -205,6 +206,12 @@ pub enum WindowEvent { // Called when the files are dropped into the window DroppedFile(Vec), + // Called when urls are dropped into the window + DroppedUrl(Vec), + + // Called when text is dropped into the window + DroppedString(String), + /// Called by menubar dispatching stuff on some systems PerformKeyAssignment(config::keyassignment::KeyAssignment), diff --git a/window/src/os/x11/connection.rs b/window/src/os/x11/connection.rs index 5e55ca98700..0a11d75d583 100644 --- a/window/src/os/x11/connection.rs +++ b/window/src/os/x11/connection.rs @@ -35,6 +35,7 @@ pub struct XConnection { pub atom_targets: Atom, pub atom_clipboard: Atom, pub atom_texturilist: Atom, + pub atom_xmozurl: Atom, pub atom_xdndaware: Atom, pub atom_xdndtypelist: Atom, pub atom_xdndselection: Atom, @@ -626,6 +627,7 @@ impl XConnection { let atom_targets = Self::intern_atom(&conn, "TARGETS")?; let atom_clipboard = Self::intern_atom(&conn, "CLIPBOARD")?; let atom_texturilist = Self::intern_atom(&conn, "text/uri-list")?; + let atom_xmozurl = Self::intern_atom(&conn, "text/x-moz-url")?; let atom_xdndaware = Self::intern_atom(&conn, "XdndAware")?; let atom_xdndtypelist = Self::intern_atom(&conn, "XdndTypeList")?; let atom_xdndselection = Self::intern_atom(&conn, "XdndSelection")?; @@ -762,6 +764,7 @@ impl XConnection { atom_protocols, atom_clipboard, atom_texturilist, + atom_xmozurl, atom_xdndaware, atom_xdndtypelist, atom_xdndselection, diff --git a/window/src/os/x11/window.rs b/window/src/os/x11/window.rs index 299415f4367..18024097a3a 100644 --- a/window/src/os/x11/window.rs +++ b/window/src/os/x11/window.rs @@ -576,10 +576,17 @@ impl XWindowInner { }; } self.drag_and_drop.target_type = xcb::x::ATOM_NONE; - for t in &self.drag_and_drop.src_types { - if *t == conn.atom_texturilist { - self.drag_and_drop.target_type = conn.atom_texturilist; + for t in [ + conn.atom_texturilist, + conn.atom_xmozurl, + conn.atom_utf8_string, + ] { + if self.drag_and_drop.src_types.contains(&t) { + self.drag_and_drop.target_type = t; + break; } + } + for t in &self.drag_and_drop.src_types { log::trace!("types offered: {}", conn.atom_name(*t)); } log::trace!( @@ -1105,7 +1112,54 @@ impl XWindowInner { long_length: u32::max_value(), }) { Ok(prop) => { - if selection.target() == conn.atom_texturilist { + if selection.target() == conn.atom_utf8_string { + let text = String::from_utf8_lossy(prop.value()).to_string(); + self.events.dispatch(WindowEvent::DroppedString(text)); + } else if selection.target() == conn.atom_xmozurl { + let raw = prop.value(); + let data; + if raw.len() >= 2 + && ((raw[0], raw[1]) == (0xfe, 0xff) + || (raw[0] != 0x00 && raw[1] == 0x00)) + { + data = String::from_utf16_lossy( + raw.chunks_exact(2) + .map(|x: &[u8]| u16::from(x[1]) << 8 | u16::from(x[0])) + .collect::>() + .as_slice(), + ); + } else if raw.len() >= 2 + && ((raw[0], raw[1]) == (0xff, 0xfe) + || (raw[0] == 0x00 && raw[1] != 0x00)) + { + data = String::from_utf16_lossy( + raw.chunks_exact(2) + .map(|x: &[u8]| u16::from(x[0]) << 8 | u16::from(x[1])) + .collect::>() + .as_slice(), + ); + } else { + data = String::from_utf8_lossy(prop.value()).to_string(); + } + use url::Url; + let urls = data + .lines() + .step_by(2) + .filter_map(|line| { + // the lines alternate between the urls and their titles + Url::parse(line) + .map_err(|err| { + log::error!( + "Error parsing dropped file line {} as url: {:#}", + line, + err + ); + }) + .ok() + }) + .collect::>(); + self.events.dispatch(WindowEvent::DroppedUrl(urls)); + } else if selection.target() == conn.atom_texturilist { let paths = String::from_utf8_lossy(prop.value()) .lines() .filter_map(|line| {