use parking_lot::Mutex; /// A Clipboard that can read and write strings. Each platform must implement this trait to support /// writing to a clipboard. pub trait Clipboard: 'static { fn write(&mut self, contents: ClipboardContent); fn read(&mut self) -> ClipboardContent; /// Writes to the primary clipboard, used to support the Primary Selection Protocol (middle-click paste). /// /// NOTE: For platforms that don't support the primary clipboard, it writes to the default clipboard instead. fn write_to_primary_clipboard(&mut self, contents: ClipboardContent) { self.write(contents) } /// Reads the primary clipboard, used to support the Primary Selection Protocol (middle-click paste). /// This reads from the default clipboard on platforms other than Linux. fn read_from_primary_clipboard(&mut self) -> ClipboardContent { self.read() } #[cfg(target_family = "wasm")] fn save(&mut self, content: ClipboardContent); } // Clipboard could contain content with multiple data types at the same type. #[derive(Debug, Clone, Default)] pub struct ClipboardContent { // Clipboard contains plain string. pub plain_text: String, // Clipboard contains a list of file paths. // Parsed direct from OS clipboard on Mac/Windows, from plain_text on Linux. // On Mac/Linux, plain_text may also be populated. pub paths: Option>, // Clipboard contains HTML content. pub html: Option, // Clipboard contains image data (can be multiple images). pub images: Option>, } /// Represents image data from the clipboard. /// /// Contains the raw image bytes and associated MIME type information. #[derive(Debug, Clone)] pub struct ImageData { /// Raw image data as bytes. pub data: Vec, /// MIME type of the image (e.g., "image/png", "image/jpeg"). pub mime_type: String, /// Original filename if available (e.g., "photo.jpg"). pub filename: Option, } impl ClipboardContent { pub fn plain_text(text: String) -> Self { Self { plain_text: text, paths: Default::default(), html: Default::default(), images: Default::default(), } } pub fn is_empty(&self) -> bool { let Self { plain_text, paths, html, images, } = self; plain_text.is_empty() && paths.is_none() && html.is_none() && images.is_none() } pub fn has_image_data(&self) -> bool { self.images .as_ref() .map(|images| !images.is_empty()) .unwrap_or(false) } pub fn num_paths(&self) -> usize { self.paths.as_ref().map(|paths| paths.len()).unwrap_or(0) } /// Check if clipboard contains file paths that are not images pub fn has_non_image_filepaths(&self) -> bool { self.paths .as_ref() .map(|paths| { paths .iter() .any(|path| !crate::clipboard_utils::has_image_extension(path)) }) .unwrap_or(false) } } pub fn should_insert_text_on_paste(content: &ClipboardContent) -> bool { // Insert any text content present when: // 1. No images at all (neither data nor paths) // 2. Has non-image files (mixed content) // 3. Has image data but no file paths (direct image paste) if !content.has_image_data() && content.num_paths() == 0 { return true; // No images at all } if content.has_non_image_filepaths() { return true; // Mixed content - user likely wants text paths } // Direct image paste - user would still want any text content present (unless paths) content.has_image_data() && content.num_paths() == 0 } /// Stores clipboard content in the heap of this process. Therefore, it is scoped to this process. /// This is not a proper implementation for a "real" platform. It's useful in tests or as a /// temporary substitute. pub struct InMemoryClipboard { clipboard_content: Mutex, } impl Default for InMemoryClipboard { fn default() -> Self { Self { clipboard_content: Mutex::new(ClipboardContent::plain_text(String::new())), } } } impl Clipboard for InMemoryClipboard { fn write(&mut self, contents: ClipboardContent) { *self.clipboard_content.lock() = contents; } fn read(&mut self) -> ClipboardContent { self.clipboard_content.lock().clone() } #[cfg(target_family = "wasm")] fn save(&mut self, _content: ClipboardContent) {} }