//! Smithay Clipboard //! //! Provides access to the Wayland clipboard for gui applications. The user //! should have surface around. #![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use)] use std::ffi::c_void; use std::io::Result; use std::sync::mpsc::{self, Receiver}; use sctk::reexports::calloop::channel::{self, Sender}; use sctk::reexports::client::backend::Backend; use sctk::reexports::client::Connection; use state::SelectionTarget; use text::Text; pub mod mime; mod state; mod text; mod worker; use mime::{AllowedMimeTypes, AsMimeTypes, MimeType}; /// Access to a Wayland clipboard. pub struct Clipboard { request_sender: Sender, request_receiver: Receiver, MimeType)>>, clipboard_thread: Option>, } impl Clipboard { /// Creates new clipboard which will be running on its own thread with its /// own event queue to handle clipboard requests. /// /// # Safety /// /// `display` must be a valid `*mut wl_display` pointer, and it must remain /// valid for as long as `Clipboard` object is alive. pub unsafe fn new(display: *mut c_void) -> Self { let backend = unsafe { Backend::from_foreign_display(display.cast()) }; let connection = Connection::from_backend(backend); // Create channel to send data to clipboard thread. let (request_sender, rx_chan) = channel::channel(); // Create channel to get data from the clipboard thread. let (clipboard_reply_sender, request_receiver) = mpsc::channel(); let name = String::from("smithay-clipboard"); let clipboard_thread = worker::spawn(name, connection, rx_chan, clipboard_reply_sender); Self { request_receiver, request_sender, clipboard_thread } } /// Load custom clipboard data. /// /// Load the requested type from a clipboard on the last observed seat. pub fn load(&self) -> Result { self.load_inner(SelectionTarget::Clipboard) } /// Load clipboard data. /// /// Loads content from a clipboard on a last observed seat. pub fn load_text(&self) -> Result { self.load::().map(|t| t.0) } /// Load custom primary clipboard data. /// /// Load the requested type from a primary clipboard on the last observed /// seat. pub fn load_primary(&self) -> Result { self.load_inner(SelectionTarget::Primary) } /// Load primary clipboard data. /// /// Loads content from a primary clipboard on a last observed seat. pub fn load_primary_text(&self) -> Result { self.load_primary::().map(|t| t.0) } /// Store custom data to a clipboard. /// /// Stores data of the provided type to a clipboard on a last observed seat. pub fn store(&self, data: T) { self.store_inner(data, SelectionTarget::Clipboard); } /// Store to a clipboard. /// /// Stores to a clipboard on a last observed seat. pub fn store_text>(&self, text: T) { self.store(Text(text.into())); } /// Store custom data to a primary clipboard. /// /// Stores data of the provided type to a primary clipboard on a last /// observed seat. pub fn store_primary(&self, data: T) { self.store_inner(data, SelectionTarget::Primary); } /// Store to a primary clipboard. /// /// Stores to a primary clipboard on a last observed seat. pub fn store_primary_text>(&self, text: T) { self.store_primary(Text(text.into())); } fn load_inner(&self, target: SelectionTarget) -> Result { let _ = self.request_sender.send(worker::Command::Load(T::allowed().to_vec(), target)); match self.request_receiver.recv() { Ok(res) => res.and_then(|(data, mime)| { T::try_from((data, mime)).map_err(|_| { std::io::Error::new( std::io::ErrorKind::Other, "Failed to load data of the requested type.", ) }) }), // The clipboard thread is dead, however we shouldn't crash downstream, // so propogating an error. Err(_) => Err(std::io::Error::new(std::io::ErrorKind::Other, "clipboard is dead.")), } } fn store_inner(&self, data: T, target: SelectionTarget) { let request = worker::Command::Store(Box::new(data), target); let _ = self.request_sender.send(request); } } impl Drop for Clipboard { fn drop(&mut self) { // Shutdown smithay-clipboard. let _ = self.request_sender.send(worker::Command::Exit); if let Some(clipboard_thread) = self.clipboard_thread.take() { let _ = clipboard_thread.join(); } } }