clipboard_wayland/
lib.rs

1// Copyright 2017 Avraham Weinstock
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    borrow::Cow,
17    error::Error,
18    ffi::c_void,
19    sync::{mpsc::SendError, Arc, Mutex},
20};
21
22use dnd::{
23    DataWrapper, DndAction, DndDestinationRectangle, DndSurface, Sender,
24};
25use mime::ClipboardData;
26use smithay_clipboard::dnd::{Icon, Rectangle};
27pub use smithay_clipboard::mime::{AllowedMimeTypes, AsMimeTypes, MimeType};
28
29#[derive(Clone)]
30pub struct DndSender(pub Arc<dyn Sender<DndSurface> + 'static + Send + Sync>);
31
32impl smithay_clipboard::dnd::Sender<DndSurface> for DndSender {
33    fn send(
34        &self,
35        event: smithay_clipboard::dnd::DndEvent<DndSurface>,
36    ) -> Result<(), SendError<smithay_clipboard::dnd::DndEvent<DndSurface>>>
37    {
38        _ = self.0.send(match event {
39            smithay_clipboard::dnd::DndEvent::Offer(id, e) => dnd::DndEvent::Offer(
40                id,
41                match e {
42                    smithay_clipboard::dnd::OfferEvent::Enter {
43                        x,
44                        y,
45                        mime_types,
46                        surface,
47                    } => dnd::OfferEvent::Enter {
48                        x,
49                        y,
50                        mime_types: mime_types
51                            .into_iter()
52                            .map(|m| m.to_string())
53                            .collect(),
54                        surface,
55                    },
56                    smithay_clipboard::dnd::OfferEvent::Motion { x, y } => {
57                        dnd::OfferEvent::Motion { x, y }
58                    }
59                    smithay_clipboard::dnd::OfferEvent::LeaveDestination => {
60                        dnd::OfferEvent::LeaveDestination
61                    }
62                    smithay_clipboard::dnd::OfferEvent::Leave => {
63                        dnd::OfferEvent::Leave
64                    }
65                    smithay_clipboard::dnd::OfferEvent::Drop => {
66                        dnd::OfferEvent::Drop
67                    }
68                    smithay_clipboard::dnd::OfferEvent::SelectedAction(
69                        action,
70                    ) => dnd::OfferEvent::SelectedAction(action.into()),
71                    smithay_clipboard::dnd::OfferEvent::Data {
72                        data,
73                        mime_type,
74                    } => dnd::OfferEvent::Data {
75                        data,
76                        mime_type: mime_type.to_string(),
77                    },
78                },
79            ),
80            smithay_clipboard::dnd::DndEvent::Source(e) => match e {
81                smithay_clipboard::dnd::SourceEvent::Finished => {
82                    dnd::DndEvent::Source(dnd::SourceEvent::Finished)
83                }
84                smithay_clipboard::dnd::SourceEvent::Cancelled => {
85                    dnd::DndEvent::Source(dnd::SourceEvent::Cancelled)
86                }
87                smithay_clipboard::dnd::SourceEvent::Action(action) => {
88                    dnd::DndEvent::Source(dnd::SourceEvent::Action(
89                        action.into(),
90                    ))
91                }
92                smithay_clipboard::dnd::SourceEvent::Mime(mime) => {
93                    dnd::DndEvent::Source(dnd::SourceEvent::Mime(
94                        mime.map(|m| m.to_string()),
95                    ))
96                }
97                smithay_clipboard::dnd::SourceEvent::Dropped => {
98                    dnd::DndEvent::Source(dnd::SourceEvent::Dropped)
99                }
100            },
101        });
102        Ok(())
103    }
104}
105
106pub struct Clipboard {
107    context: Arc<Mutex<smithay_clipboard::Clipboard<DndSurface>>>,
108}
109
110impl Clipboard {
111    pub unsafe fn connect(display: *mut c_void) -> Clipboard {
112        let context = Arc::new(Mutex::new(smithay_clipboard::Clipboard::new(
113            display as *mut _,
114        )));
115
116        Clipboard { context }
117    }
118
119    pub fn read(&self) -> Result<String, Box<dyn Error>> {
120        Ok(self.context.lock().unwrap().load_text()?)
121    }
122
123    pub fn read_primary(&self) -> Result<String, Box<dyn Error>> {
124        Ok(self.context.lock().unwrap().load_primary_text()?)
125    }
126
127    pub fn write(&mut self, data: String) -> Result<(), Box<dyn Error>> {
128        self.context.lock().unwrap().store_text(data);
129
130        Ok(())
131    }
132
133    pub fn write_primary(
134        &mut self,
135        data: String,
136    ) -> Result<(), Box<dyn Error>> {
137        self.context.lock().unwrap().store_primary_text(data);
138
139        Ok(())
140    }
141
142    pub fn write_data<T: AsMimeTypes + Send + Sync + 'static>(
143        &mut self,
144        data: T,
145    ) -> Result<(), Box<dyn Error>> {
146        self.context.lock().unwrap().store(data);
147
148        Ok(())
149    }
150
151    pub fn write_primary_data<T: AsMimeTypes + Send + Sync + 'static>(
152        &mut self,
153        data: T,
154    ) -> Result<(), Box<dyn Error>> {
155        self.context.lock().unwrap().store_primary(data);
156
157        Ok(())
158    }
159
160    pub fn read_data<T: AllowedMimeTypes + 'static>(
161        &self,
162    ) -> Result<T, Box<dyn Error>> {
163        Ok(self.context.lock().unwrap().load()?)
164    }
165
166    pub fn read_primary_data<T: AllowedMimeTypes + 'static>(
167        &self,
168    ) -> Result<T, Box<dyn Error>> {
169        Ok(self.context.lock().unwrap().load_primary()?)
170    }
171
172    pub fn read_primary_raw(
173        &self,
174        allowed: Vec<String>,
175    ) -> Result<(Vec<u8>, String), Box<dyn Error>> {
176        Ok(self
177            .context
178            .lock()
179            .unwrap()
180            .load_primary_mime::<DataWrapper<ClipboardData>>(
181                allowed
182                    .into_iter()
183                    .map(|s| MimeType::from(Cow::Owned(s)))
184                    .collect::<Vec<_>>(),
185            )
186            .map(|d| (d.0 .0, d.0 .1.to_string()))?)
187    }
188
189    pub fn read_raw(
190        &self,
191        allowed: Vec<String>,
192    ) -> Result<(Vec<u8>, String), Box<dyn Error>> {
193        Ok(self
194            .context
195            .lock()
196            .unwrap()
197            .load_mime::<DataWrapper<ClipboardData>>(
198                allowed
199                    .into_iter()
200                    .map(|s| MimeType::from(Cow::Owned(s)))
201                    .collect::<Vec<_>>(),
202            )
203            .map(|d| (d.0 .0, d.0 .1))?)
204    }
205
206    pub fn init_dnd(&self, tx: DndSender) {
207        _ = self.context.lock().unwrap().init_dnd(Box::new(tx));
208    }
209
210    /// Start a DnD operation on the given surface with some data
211    pub fn start_dnd<D: mime::AsMimeTypes + Send + 'static>(
212        &self,
213        internal: bool,
214        source_surface: DndSurface,
215        icon_surface: Option<dnd::Icon>,
216        content: D,
217        actions: DndAction,
218    ) {
219        _ = self.context.lock().unwrap().start_dnd(
220            internal,
221            source_surface,
222            icon_surface.map(|i| Icon::<DndSurface>::from(i)),
223            DataWrapper(content),
224            actions.into(),
225        );
226    }
227
228    /// End the current DnD operation, if there is one
229    pub fn end_dnd(&self) {
230        _ = self.context.lock().unwrap().end_dnd();
231    }
232
233    /// Register a surface for receiving DnD offers
234    /// Rectangles should be provided in order of decreasing priority.
235    /// This method can be called multiple time for a single surface if the
236    /// rectangles change.
237    pub fn register_dnd_destination(
238        &self,
239        surface: DndSurface,
240        rectangles: Vec<DndDestinationRectangle>,
241    ) {
242        _ = self.context.lock().unwrap().register_dnd_destination(
243            surface,
244            rectangles
245                .into_iter()
246                .map(|r| RectangleWrapper(r).into())
247                .collect(),
248        );
249    }
250
251    /// Set the final action after presenting the user with a choice
252    pub fn set_action(&self, action: DndAction) {
253        self.context.lock().unwrap().set_action(action.into());
254    }
255
256    /// Peek at the contents of a DnD offer
257    pub fn peek_offer<D: mime::AllowedMimeTypes + 'static>(
258        &self,
259        mime_type: Option<Cow<'static, str>>,
260    ) -> std::io::Result<D> {
261        let d = self
262            .context
263            .lock()
264            .unwrap()
265            .peek_offer::<DataWrapper<D>>(mime_type.map(MimeType::from));
266        d.map(|d| d.0)
267    }
268}
269
270pub struct RectangleWrapper(pub DndDestinationRectangle);
271
272impl From<RectangleWrapper>
273    for smithay_clipboard::dnd::DndDestinationRectangle
274{
275    fn from(RectangleWrapper(d): RectangleWrapper) -> Self {
276        smithay_clipboard::dnd::DndDestinationRectangle {
277            id: d.id,
278            rectangle: Rectangle {
279                x: d.rectangle.x,
280                y: d.rectangle.y,
281                width: d.rectangle.width,
282                height: d.rectangle.height,
283            },
284            mime_types: d.mime_types.into_iter().map(MimeType::from).collect(),
285            actions: d.actions.into(),
286            preferred: d.preferred.into(),
287        }
288    }
289}