Skip to main content

floem/platform/
clipboard.rs

1use parking_lot::Mutex;
2use raw_window_handle::RawDisplayHandle;
3
4use copypasta::{ClipboardContext, ClipboardProvider};
5
6static CLIPBOARD: Mutex<Option<Clipboard>> = Mutex::new(None);
7
8pub struct Clipboard {
9    clipboard: Box<dyn ClipboardProvider>,
10    #[allow(dead_code)]
11    selection: Option<Box<dyn ClipboardProvider>>,
12}
13
14#[derive(Clone, Debug)]
15pub enum ClipboardError {
16    NotAvailable,
17    ProviderError(String),
18    PathError(String),
19}
20
21impl Clipboard {
22    pub fn get_contents() -> Result<String, ClipboardError> {
23        CLIPBOARD
24            .lock()
25            .as_mut()
26            .ok_or(ClipboardError::NotAvailable)?
27            .clipboard
28            .get_contents()
29            .map_err(|e| ClipboardError::ProviderError(e.to_string()))
30    }
31
32    pub fn set_contents(s: String) -> Result<(), ClipboardError> {
33        if s.is_empty() {
34            return Err(ClipboardError::ProviderError(
35                "content is empty".to_string(),
36            ));
37        }
38        CLIPBOARD
39            .lock()
40            .as_mut()
41            .ok_or(ClipboardError::NotAvailable)?
42            .clipboard
43            .set_contents(s)
44            .map_err(|e| ClipboardError::ProviderError(e.to_string()))
45    }
46
47    #[cfg(windows)]
48    pub fn get_file_list() -> Result<Vec<std::path::PathBuf>, ClipboardError> {
49        use std::{path::PathBuf, str::FromStr};
50
51        let mut out: Vec<String> = Vec::new();
52        clipboard_win::raw::get_file_list(&mut out)
53            .map_err(|e| ClipboardError::ProviderError(e.to_string()))?;
54        out.iter()
55            .map(|s| PathBuf::from_str(s).map_err(|e| ClipboardError::PathError(e.to_string())))
56            .collect()
57    }
58
59    pub(crate) unsafe fn init(display: RawDisplayHandle) {
60        *CLIPBOARD.lock() = Some(unsafe { Self::new(display) });
61    }
62
63    /// # Safety
64    /// The `display` must be valid as long as the returned Clipboard exists.
65    unsafe fn new(
66        #[allow(unused_variables)] /* on some platforms */ display: RawDisplayHandle,
67    ) -> Self {
68        #[cfg(not(any(
69            target_os = "macos",
70            target_os = "windows",
71            target_os = "ios",
72            target_os = "android",
73            target_arch = "wasm32"
74        )))]
75        {
76            if let RawDisplayHandle::Wayland(display) = display {
77                use copypasta::wayland_clipboard;
78                let (selection, clipboard) = unsafe {
79                    wayland_clipboard::create_clipboards_from_external(display.display.as_ptr())
80                };
81                return Self {
82                    clipboard: Box::new(clipboard),
83                    selection: Some(Box::new(selection)),
84                };
85            }
86
87            use copypasta::x11_clipboard::{Primary, X11ClipboardContext};
88            Self {
89                clipboard: Box::new(ClipboardContext::new().unwrap()),
90                selection: Some(Box::new(X11ClipboardContext::<Primary>::new().unwrap())),
91            }
92        }
93
94        // TODO: Implement clipboard support for the web, ios, and android
95        #[cfg(any(
96            target_os = "macos",
97            target_os = "windows",
98            target_os = "ios",
99            target_os = "android",
100            target_arch = "wasm32"
101        ))]
102        return Self {
103            clipboard: Box::new(ClipboardContext::new().unwrap()),
104            selection: None,
105        };
106    }
107}