diff --git a/window/src/os/wayland/copy_and_paste.rs b/window/src/os/wayland/copy_and_paste.rs index 70bb855af..34142f4e0 100644 --- a/window/src/os/wayland/copy_and_paste.rs +++ b/window/src/os/wayland/copy_and_paste.rs @@ -15,25 +15,29 @@ use crate::{Clipboard, ConnectionOps}; use super::data_device::TEXT_MIME_TYPE; use super::state::WaylandState; +/// Holds the most recent clipboard offer received from another Wayland client. +/// Updated on every `wl_data_device::selection` event. #[derive(Default)] -pub struct CopyAndPaste { +pub struct CopyPasteOffer { data_offer: Option, } -impl std::fmt::Debug for CopyAndPaste { +impl std::fmt::Debug for CopyPasteOffer { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - fmt.debug_struct("CopyAndPaste") + fmt.debug_struct("CopyPasteOffer") .field("data_offer", &self.data_offer.is_some()) .finish() } } -impl CopyAndPaste { +impl CopyPasteOffer { + /// Creates a new empty offer. pub(super) fn create() -> Arc> { Arc::new(Mutex::new(Default::default())) } - pub(super) fn get_clipboard_data(&mut self, clipboard: Clipboard) -> anyhow::Result { + /// Returns a pipe to read the current clipboard contents for the given clipboard type. + pub(super) fn get_clipboard_data(&self, clipboard: Clipboard) -> anyhow::Result { let conn = crate::Connection::get().unwrap().wayland(); let wayland_state = conn.wayland_state.borrow(); let primary_selection = if let Clipboard::PrimarySelection = clipboard { @@ -62,6 +66,7 @@ impl CopyAndPaste { } } + /// Claims clipboard ownership and advertises `data` as the current selection. pub(super) fn set_clipboard_data(&mut self, clipboard: Clipboard, data: String) { let conn = crate::Connection::get().unwrap().wayland(); let qh = conn.event_queue.borrow().handle(); @@ -94,6 +99,7 @@ impl CopyAndPaste { } } + /// Stores the latest selection offer from the compositor, replacing any previous one. pub(super) fn confirm_selection(&mut self, offer: SelectionOffer) { self.data_offer.replace(offer); } diff --git a/window/src/os/wayland/data_device.rs b/window/src/os/wayland/data_device.rs index 8af5194bc..dff24aa9e 100644 --- a/window/src/os/wayland/data_device.rs +++ b/window/src/os/wayland/data_device.rs @@ -111,19 +111,12 @@ impl DataDeviceHandler for WaylandState { if !offer.with_mime_types(|mime_types| mime_types.iter().any(|s| s == TEXT_MIME_TYPE)) { return; } - - // The compositor sends the (copy) selection event once per client. - // Broadcast to every window so any of them can paste, regardless - // of which surface was active when the clipboard changed. + // The compositor sends the selection event once per client. // ref: https://github.com/wezterm/wezterm/issues/6685 - for pending_mouse in self.surface_to_pending.values() { - let copy_and_paste = &pending_mouse.lock().unwrap().copy_and_paste; - // Each window gets a clone of the same underlying WlDataOffer handle. - copy_and_paste - .lock() - .unwrap() - .confirm_selection(offer.clone()); - } + self.copy_paste_offer + .lock() + .unwrap() + .confirm_selection(offer); } } diff --git a/window/src/os/wayland/pointer.rs b/window/src/os/wayland/pointer.rs index 3436d17f4..394face37 100644 --- a/window/src/os/wayland/pointer.rs +++ b/window/src/os/wayland/pointer.rs @@ -15,7 +15,6 @@ use wezterm_input_types::MousePress; use crate::wayland::SurfaceUserData; -use super::copy_and_paste::CopyAndPaste; use super::drag_and_drop::DragAndDrop; use super::state::WaylandState; use super::WaylandConnection; @@ -92,7 +91,6 @@ impl PointerDataExt for PointerUserData { #[derive(Clone, Debug)] pub struct PendingMouse { window_id: usize, - pub(super) copy_and_paste: Arc>, surface_coords: Option<(f64, f64)>, button: Vec<(MousePress, ButtonState)>, scroll: Option<(f64, f64)>, @@ -100,13 +98,9 @@ pub struct PendingMouse { } impl PendingMouse { - pub(super) fn create( - window_id: usize, - copy_and_paste: &Arc>, - ) -> Arc> { + pub(super) fn create(window_id: usize) -> Arc> { Arc::new(Mutex::new(Self { window_id, - copy_and_paste: Arc::clone(copy_and_paste), button: vec![], scroll: None, surface_coords: None, diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index d8067d708..31885b58d 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -36,6 +36,7 @@ use wayland_protocols_plasma::blur::client::org_kde_kwin_blur_manager::OrgKdeKwi use crate::x11::KeyboardWithFallback; +use super::copy_and_paste::CopyPasteOffer; use super::inputhandler::{TextInputData, TextInputState}; use super::pointer::{PendingMouse, PointerUserData}; use super::{OutputManagerData, OutputManagerState, SurfaceUserData, WaylandWindowInner}; @@ -64,12 +65,27 @@ pub(super) struct WaylandState { pub(super) pointer: Option>, pub(super) surface_to_pending: HashMap>>, + /// Bound global for wl_data_device_manager; factory for data devices and copy-paste sources. pub(super) data_device_manager_state: DataDeviceManagerState, + /// Protocol object for clipboard and drag-and-drop. + /// None until first seat capability event is received. pub(super) data_device: Option, + /// Most recent incoming clipboard offer from another Wayland client (regular clipboard). + /// Updated on every `wl_data_device::selection` event; read when pasting data. + pub(super) copy_paste_offer: Arc>, + /// Outgoing regular clipboard: our data source while we own clipboard selection. + /// Dropped when another Wayland client takes ownership. pub(super) copy_paste_source: Option<(CopyPasteSource, String)>, + /// Protocol manager for primary selection. + /// None if unsupported by compositor. pub(super) primary_selection_manager: Option, + /// Protocol object for primary selection device. + /// None if unsupported by compositor, or until first seat capability event is received. pub(super) primary_selection_device: Option, + /// Outgoing primary selection: our source while we own primary selection. + /// Dropped when another client takes ownership. pub(super) primary_selection_source: Option<(PrimarySelectionSource, String)>, + pub(super) shm: Shm, pub(super) mem_pool: RefCell, pub(super) kde_blur_manager: Option, @@ -110,6 +126,7 @@ impl WaylandState { surface_to_pending: HashMap::new(), data_device_manager_state: DataDeviceManagerState::bind(globals, qh)?, data_device: None, + copy_paste_offer: CopyPasteOffer::create(), copy_paste_source: None, primary_selection_manager: PrimarySelectionManagerState::bind(globals, qh).ok(), primary_selection_device: None, diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index b76a7691f..9c2a11c4f 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -81,7 +81,6 @@ impl WaylandDimensions for Dimensions { } } -use super::copy_and_paste::CopyAndPaste; use super::pointer::{PendingMouse, PointerUserData}; use super::state::WaylandState; @@ -286,8 +285,7 @@ impl WaylandWindow { .set_window_geometry(x, y, surface_width, surface_height); window.commit(); - let copy_and_paste = CopyAndPaste::create(); - let pending_mouse = PendingMouse::create(window_id, ©_and_paste); + let pending_mouse = PendingMouse::create(window_id); { let surface_to_pending = &mut conn.wayland_state.borrow_mut().surface_to_pending; @@ -299,7 +297,6 @@ impl WaylandWindow { let inner = Rc::new(RefCell::new(WaylandWindowInner { events: WindowEventSender::new(event_handler), surface_factor: 1.0, - copy_and_paste, invalidated: false, window: Some(window), window_frame, @@ -446,42 +443,55 @@ impl WindowOps for WaylandWindow { let mut promise = Promise::new(); let future = promise.get_future().unwrap(); let promise = Arc::new(Mutex::new(promise)); - WaylandConnection::with_window_inner(self.0, move |inner| { - let read = inner - .copy_and_paste + promise::spawn::spawn_into_main_thread(async move { + let conn = crate::Connection::get().unwrap().wayland(); + // Clone the Arc before dropping the borrow so get_clipboard_data can re-borrow + // wayland_state internally (so we don't have to pass all state manually). + let copy_paste_offer = conn.wayland_state.borrow().copy_paste_offer.clone(); + match copy_paste_offer .lock() .unwrap() - .get_clipboard_data(clipboard)?; - let promise = Arc::clone(&promise); - std::thread::spawn(move || { - let mut promise = promise.lock().unwrap(); - match read_pipe_with_timeout(read) { - Ok(result) => { - // Normalize the text to unix line endings, otherwise - // copying from eg: firefox inserts a lot of blank - // lines, and that is super annoying. - promise.ok(result.replace("\r\n", "\n")); - } - Err(e) => { - log::error!("while reading clipboard: {}", e); - promise.err(anyhow!("{}", e)); - } - }; - }); - Ok(()) - }); + .get_clipboard_data(clipboard) + { + Ok(read) => { + std::thread::spawn(move || { + let mut promise = promise.lock().unwrap(); + match read_pipe_with_timeout(read) { + Ok(result) => { + // Normalize the text to unix line endings, otherwise + // copying from eg: firefox inserts a lot of blank + // lines, and that is super annoying. + promise.ok(result.replace("\r\n", "\n")); + } + Err(e) => { + log::error!("while reading clipboard: {}", e); + promise.err(anyhow!("{}", e)); + } + }; + }); + } + Err(e) => { + // Report the error on the Promise + promise.lock().unwrap().err(e); + } + }; + }) + .detach(); future } fn set_clipboard(&self, clipboard: Clipboard, text: String) { - WaylandConnection::with_window_inner(self.0, move |inner| { - inner - .copy_and_paste + promise::spawn::spawn_into_main_thread(async move { + let conn = crate::Connection::get().unwrap().wayland(); + // Clone the Arc before dropping the borrow so set_clipboard_data can re-borrow + // wayland_state internally (so we don't have to pass all state manually). + let copy_paste_offer = conn.wayland_state.borrow().copy_paste_offer.clone(); + copy_paste_offer .lock() .unwrap() .set_clipboard_data(clipboard, text); - Ok(()) - }); + }) + .detach(); } fn toggle_fullscreen(&self) { @@ -566,7 +576,6 @@ pub(crate) fn read_pipe_with_timeout(mut file: ReadPipe) -> anyhow::Result>, window: Option, pub(super) window_frame: FallbackFrame, dimensions: Dimensions,