Skip to main content

argui_platform/
clipboard.rs

1use std::{error::Error, fmt};
2
3#[derive(Clone, Debug, Eq, PartialEq)]
4pub struct ClipboardError(String);
5
6impl fmt::Display for ClipboardError {
7    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
8        formatter.write_str(&self.0)
9    }
10}
11
12impl Error for ClipboardError {}
13
14#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
15#[derive(Default)]
16pub struct Clipboard {
17    inner: Option<arboard::Clipboard>,
18}
19
20#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
21#[cfg_attr(coverage_nightly, coverage(off))]
22impl Clipboard {
23    #[must_use]
24    pub const fn new() -> Self {
25        Self { inner: None }
26    }
27
28    pub fn read_text(&mut self) -> Result<String, ClipboardError> {
29        self.inner()?.get_text().map_err(error)
30    }
31
32    pub fn write_text(&mut self, text: String) -> Result<(), ClipboardError> {
33        self.inner()?.set_text(text).map_err(error)
34    }
35
36    fn inner(&mut self) -> Result<&mut arboard::Clipboard, ClipboardError> {
37        if self.inner.is_none() {
38            self.inner = Some(arboard::Clipboard::new().map_err(error)?);
39        }
40        Ok(self.inner.as_mut().expect("clipboard was initialized"))
41    }
42}
43
44#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
45fn error(error: arboard::Error) -> ClipboardError {
46    ClipboardError(error.to_string())
47}
48
49#[cfg(all(
50    not(target_arch = "wasm32"),
51    not(any(target_os = "linux", target_os = "windows", target_os = "macos"))
52))]
53#[derive(Default)]
54pub struct Clipboard;
55
56#[cfg(all(
57    not(target_arch = "wasm32"),
58    not(any(target_os = "linux", target_os = "windows", target_os = "macos"))
59))]
60impl Clipboard {
61    #[must_use]
62    pub const fn new() -> Self {
63        Self
64    }
65
66    pub fn read_text(&mut self) -> Result<String, ClipboardError> {
67        Err(ClipboardError(
68            "clipboard integration is unavailable on this target".into(),
69        ))
70    }
71
72    pub fn write_text(&mut self, _text: String) -> Result<(), ClipboardError> {
73        Err(ClipboardError(
74            "clipboard integration is unavailable on this target".into(),
75        ))
76    }
77}
78
79#[cfg(target_arch = "wasm32")]
80#[derive(Default)]
81pub struct Clipboard;
82
83#[cfg(target_arch = "wasm32")]
84#[cfg_attr(coverage_nightly, coverage(off))]
85impl Clipboard {
86    #[must_use]
87    pub const fn new() -> Self {
88        Self
89    }
90
91    pub async fn read_text(&mut self) -> Result<String, ClipboardError> {
92        let clipboard = web_sys::window()
93            .ok_or_else(|| ClipboardError("browser window is unavailable".into()))?
94            .navigator()
95            .clipboard();
96        let value = wasm_bindgen_futures::JsFuture::from(clipboard.read_text())
97            .await
98            .map_err(|value| ClipboardError(format!("{value:?}")))?;
99        value
100            .as_string()
101            .ok_or_else(|| ClipboardError("clipboard returned non-text data".into()))
102    }
103
104    pub async fn write_text(&mut self, text: String) -> Result<(), ClipboardError> {
105        let clipboard = web_sys::window()
106            .ok_or_else(|| ClipboardError("browser window is unavailable".into()))?
107            .navigator()
108            .clipboard();
109        wasm_bindgen_futures::JsFuture::from(clipboard.write_text(&text))
110            .await
111            .map_err(|value| ClipboardError(format!("{value:?}")))?;
112        Ok(())
113    }
114}