1use std::sync::Mutex;
4use std::{any::Any, borrow::Cow};
5
6use crate::core::clipboard::Kind;
7use crate::core::clipboard::{DndSource, DynIconSurface};
8use std::sync::Arc;
9use winit::dpi::LogicalSize;
10use winit::window::{Window, WindowId};
11
12use dnd::{DndAction, DndDestinationRectangle, DndSurface, Icon};
13use window_clipboard::{
14 dnd::DndProvider,
15 mime::{self, ClipboardData, ClipboardStoreData},
16};
17
18#[allow(missing_debug_implementations)]
21pub struct Clipboard {
22 state: State,
23 pub(crate) requested_logical_size: Arc<Mutex<Option<LogicalSize<f32>>>>,
24}
25
26pub(crate) struct StartDnd {
27 pub(crate) internal: bool,
28 pub(crate) source_surface: Option<DndSource>,
29 pub(crate) icon_surface: Option<DynIconSurface>,
30 pub(crate) content: Box<dyn mime::AsMimeTypes + Send + 'static>,
31 pub(crate) actions: DndAction,
32}
33
34enum State {
35 Connected {
36 clipboard: window_clipboard::Clipboard,
37 sender: ControlSender,
38 #[allow(dead_code)]
43 window: Arc<dyn Window>,
44 queued_events: Vec<StartDnd>,
45 },
46 Unavailable,
47}
48
49#[derive(Debug, Clone)]
50pub(crate) struct ControlSender {
51 pub(crate) sender: iced_futures::futures::channel::mpsc::UnboundedSender<
52 crate::program::Control,
53 >,
54 pub(crate) proxy: winit::event_loop::EventLoopProxy,
55}
56
57impl dnd::Sender<DndSurface> for ControlSender {
58 fn send(
59 &self,
60 event: dnd::DndEvent<DndSurface>,
61 ) -> Result<(), std::sync::mpsc::SendError<dnd::DndEvent<DndSurface>>> {
62 let res = self
63 .sender
64 .unbounded_send(crate::program::Control::Dnd(event))
65 .map_err(|_err| {
66 std::sync::mpsc::SendError(dnd::DndEvent::Offer(
67 None,
68 dnd::OfferEvent::Leave,
69 ))
70 });
71 self.proxy.wake_up();
72 res
73 }
74}
75
76impl Clipboard {
77 pub(crate) fn connect(
79 window: Arc<dyn Window>,
80 sender: ControlSender,
81 ) -> Clipboard {
82 #[allow(unsafe_code)]
83 let state =
84 unsafe { window_clipboard::Clipboard::connect(window.as_ref()) }
85 .ok()
86 .map(|c| State::Connected {
87 clipboard: c,
88 sender: sender.clone(),
89 window,
90 queued_events: Vec::new(),
91 })
92 .unwrap_or(State::Unavailable);
93
94 #[cfg(target_os = "linux")]
95 if let State::Connected { clipboard, .. } = &state {
96 clipboard.init_dnd(Box::new(sender));
97 }
98
99 Clipboard {
100 state,
101 requested_logical_size: Arc::new(Mutex::new(None)),
102 }
103 }
104
105 pub(crate) fn proxy(&self) -> Option<winit::event_loop::EventLoopProxy> {
106 if let State::Connected {
107 sender: ControlSender { proxy, .. },
108 ..
109 } = &self.state
110 {
111 Some(proxy.clone())
112 } else {
113 None
114 }
115 }
116
117 pub fn unconnected() -> Clipboard {
120 Clipboard {
121 state: State::Unavailable,
122 requested_logical_size: Arc::new(Mutex::new(None)),
123 }
124 }
125
126 pub(crate) fn get_queued(&mut self) -> Vec<StartDnd> {
127 match &mut self.state {
128 State::Connected { queued_events, .. } => {
129 std::mem::take(queued_events)
130 }
131 State::Unavailable => {
132 log::error!("Invalid request for queued dnd events");
133 Vec::<StartDnd>::new()
134 }
135 }
136 }
137
138 pub fn read(&self, kind: Kind) -> Option<String> {
140 match &self.state {
141 State::Connected { clipboard, .. } => match kind {
142 Kind::Standard => clipboard.read().ok(),
143 Kind::Primary => clipboard.read_primary().and_then(Result::ok),
144 },
145 State::Unavailable => None,
146 }
147 }
148
149 pub fn write(&mut self, kind: Kind, contents: String) {
151 match &mut self.state {
152 State::Connected { clipboard, .. } => {
153 let result = match kind {
154 Kind::Standard => clipboard.write(contents),
155 Kind::Primary => {
156 clipboard.write_primary(contents).unwrap_or(Ok(()))
157 }
158 };
159
160 match result {
161 Ok(()) => {}
162 Err(error) => {
163 log::warn!("error writing to clipboard: {error}");
164 }
165 }
166 }
167 State::Unavailable => {}
168 }
169 }
170
171 pub fn window_id(&self) -> Option<WindowId> {
173 match &self.state {
174 State::Connected { window, .. } => Some(window.id()),
175 State::Unavailable => None,
176 }
177 }
178
179 pub(crate) fn start_dnd_winit(
180 &self,
181 internal: bool,
182 source_surface: DndSurface,
183 icon_surface: Option<Icon>,
184 content: Box<dyn mime::AsMimeTypes + Send + 'static>,
185 actions: DndAction,
186 ) {
187 match &self.state {
188 State::Connected { clipboard, .. } => {
189 _ = clipboard.start_dnd(
190 internal,
191 source_surface,
192 icon_surface,
193 content,
194 actions,
195 )
196 }
197 State::Unavailable => {}
198 }
199 }
200}
201
202impl crate::core::Clipboard for Clipboard {
203 fn read(&self, kind: Kind) -> Option<String> {
204 match (&self.state, kind) {
205 (State::Connected { clipboard, .. }, Kind::Standard) => {
206 clipboard.read().ok()
207 }
208 (State::Connected { clipboard, .. }, Kind::Primary) => {
209 clipboard.read_primary().and_then(|res| res.ok())
210 }
211 (State::Unavailable, _) => None,
212 }
213 }
214
215 fn write(&mut self, kind: Kind, contents: String) {
216 match (&mut self.state, kind) {
217 (State::Connected { clipboard, .. }, Kind::Standard) => {
218 _ = clipboard.write(contents)
219 }
220 (State::Connected { clipboard, .. }, Kind::Primary) => {
221 _ = clipboard.write_primary(contents)
222 }
223 (State::Unavailable, _) => {}
224 }
225 }
226 fn read_data(
227 &self,
228 kind: Kind,
229 mimes: Vec<String>,
230 ) -> Option<(Vec<u8>, String)> {
231 match (&self.state, kind) {
232 (State::Connected { clipboard, .. }, Kind::Standard) => {
233 clipboard.read_raw(mimes).and_then(|res| res.ok())
234 }
235 (State::Connected { clipboard, .. }, Kind::Primary) => {
236 clipboard.read_primary_raw(mimes).and_then(|res| res.ok())
237 }
238 (State::Unavailable, _) => None,
239 }
240 }
241
242 fn write_data(
243 &mut self,
244 kind: Kind,
245 contents: ClipboardStoreData<
246 Box<dyn Send + Sync + 'static + mime::AsMimeTypes>,
247 >,
248 ) {
249 match (&mut self.state, kind) {
250 (State::Connected { clipboard, .. }, Kind::Standard) => {
251 _ = clipboard.write_data(contents)
252 }
253 (State::Connected { clipboard, .. }, Kind::Primary) => {
254 _ = clipboard.write_primary_data(contents)
255 }
256 (State::Unavailable, _) => {}
257 }
258 }
259
260 fn start_dnd(
261 &mut self,
262 internal: bool,
263 source_surface: Option<DndSource>,
264 icon_surface: Option<DynIconSurface>,
265 content: Box<dyn mime::AsMimeTypes + Send + 'static>,
266 actions: DndAction,
267 ) {
268 match &mut self.state {
269 State::Connected {
270 queued_events,
271 sender,
272 ..
273 } => {
274 _ = sender
275 .sender
276 .unbounded_send(crate::program::Control::StartDnd);
277 queued_events.push(StartDnd {
278 internal,
279 source_surface,
280 icon_surface,
281 content,
282 actions,
283 });
284 }
285 State::Unavailable => {}
286 }
287 }
288
289 fn register_dnd_destination(
290 &self,
291 surface: DndSurface,
292 rectangles: Vec<DndDestinationRectangle>,
293 ) {
294 match &self.state {
295 State::Connected { clipboard, .. } => {
296 _ = clipboard.register_dnd_destination(surface, rectangles)
297 }
298 State::Unavailable => {}
299 }
300 }
301
302 fn end_dnd(&self) {
303 match &self.state {
304 State::Connected { clipboard, .. } => _ = clipboard.end_dnd(),
305 State::Unavailable => {}
306 }
307 }
308
309 fn peek_dnd(&self, mime: String) -> Option<(Vec<u8>, String)> {
310 match &self.state {
311 State::Connected { clipboard, .. } => clipboard
312 .peek_offer::<ClipboardData>(Some(Cow::Owned(mime)))
313 .ok()
314 .map(|res| (res.0, res.1)),
315 State::Unavailable => None,
316 }
317 }
318
319 fn set_action(&self, action: DndAction) {
320 match &self.state {
321 State::Connected { clipboard, .. } => {
322 _ = clipboard.set_action(action)
323 }
324 State::Unavailable => {}
325 }
326 }
327
328 fn request_logical_window_size(&self, width: f32, height: f32) {
329 let mut logical_size = self.requested_logical_size.lock().unwrap();
330 *logical_size = Some(LogicalSize::new(width, height));
331 }
332}