1use crate::core::clipboard::{Content, Error, Kind};
3
4pub use platform::*;
5
6impl Default for Clipboard {
7 fn default() -> Self {
8 Self::new()
9 }
10}
11
12#[cfg(not(target_arch = "wasm32"))]
13mod platform {
14 use super::*;
15
16 use std::sync::{Arc, Mutex};
17 use std::thread;
18
19 pub struct Clipboard {
22 state: State,
23 }
24
25 enum State {
26 Connected {
27 clipboard: Arc<Mutex<arboard::Clipboard>>,
28 },
29 Unavailable,
30 }
31
32 impl Clipboard {
33 pub fn new() -> Self {
35 let clipboard = arboard::Clipboard::new();
36
37 let state = match clipboard {
38 Ok(clipboard) => State::Connected {
39 clipboard: Arc::new(Mutex::new(clipboard)),
40 },
41 Err(_) => State::Unavailable,
42 };
43
44 Clipboard { state }
45 }
46
47 pub fn read(
49 &self,
50 kind: Kind,
51 callback: impl FnOnce(Result<Content, Error>) + Send + 'static,
52 ) {
53 let State::Connected { clipboard } = &self.state else {
54 callback(Err(Error::ClipboardUnavailable));
55 return;
56 };
57
58 let clipboard = clipboard.clone();
59
60 let _ = thread::spawn(move || {
61 let Ok(mut clipboard) = clipboard.lock() else {
62 callback(Err(Error::ClipboardUnavailable));
63 return;
64 };
65
66 let get = clipboard.get();
67
68 let result = match kind {
69 Kind::Text => get.text().map(Content::Text),
70 Kind::Html => get.html().map(Content::Html),
71 #[cfg(feature = "image")]
72 Kind::Image => get.image().map(|image| {
73 let rgba = crate::core::Bytes::from_owner(image.bytes);
74 let size = crate::core::Size {
75 width: image.width as u32,
76 height: image.height as u32,
77 };
78
79 Content::Image(crate::core::clipboard::Image { rgba, size })
80 }),
81 Kind::Files => get.file_list().map(Content::Files),
82 kind => {
83 log::warn!("unsupported clipboard kind: {kind:?}");
84
85 Err(arboard::Error::ContentNotAvailable)
86 }
87 }
88 .map_err(to_error);
89
90 callback(result);
91 });
92 }
93
94 pub fn write(
96 &mut self,
97 content: Content,
98 callback: impl FnOnce(Result<(), Error>) + Send + 'static,
99 ) {
100 let State::Connected { clipboard } = &self.state else {
101 callback(Err(Error::ClipboardUnavailable));
102 return;
103 };
104
105 let clipboard = clipboard.clone();
106
107 let _ = thread::spawn(move || {
108 let Ok(mut clipboard) = clipboard.lock() else {
109 callback(Err(Error::ClipboardUnavailable));
110 return;
111 };
112
113 let set = clipboard.set();
114
115 let result = match content {
116 Content::Text(text) => set.text(text),
117 Content::Html(html) => set.html(html, None),
118 #[cfg(feature = "image")]
119 Content::Image(image) => set.image(arboard::ImageData {
120 bytes: image.rgba.as_ref().into(),
121 width: image.size.width as usize,
122 height: image.size.height as usize,
123 }),
124 Content::Files(files) => set.file_list(&files),
125 content => {
126 log::warn!("unsupported clipboard content: {content:?}");
127
128 Err(arboard::Error::ClipboardNotSupported)
129 }
130 }
131 .map_err(to_error);
132
133 callback(result);
134 });
135 }
136 }
137
138 fn to_error(error: arboard::Error) -> Error {
139 match error {
140 arboard::Error::ContentNotAvailable => Error::ContentNotAvailable,
141 arboard::Error::ClipboardNotSupported => Error::ClipboardUnavailable,
142 arboard::Error::ClipboardOccupied => Error::ClipboardOccupied,
143 arboard::Error::ConversionFailure => Error::ConversionFailure,
144 arboard::Error::Unknown { description } => Error::Unknown {
145 description: Arc::new(description),
146 },
147 error => Error::Unknown {
148 description: Arc::new(error.to_string()),
149 },
150 }
151 }
152}
153
154#[cfg(target_arch = "wasm32")]
156mod platform {
157 use super::*;
158
159 pub struct Clipboard;
162
163 impl Clipboard {
164 pub fn new() -> Self {
166 Self
167 }
168
169 pub fn read(&self, _kind: Kind, callback: impl FnOnce(Result<Content, Error>)) {
171 callback(Err(Error::ClipboardUnavailable));
172 }
173
174 pub fn write(&mut self, _content: Content, callback: impl FnOnce(Result<(), Error>)) {
176 callback(Err(Error::ClipboardUnavailable));
177 }
178 }
179}