wayland_clipboard_listener/
lib.rs

1//! ## General Induction
2//! By default, this library uses the `ext-data-control-v1` protocol. This is the standard protocol
3//! supported by modern compositors.
4//!
5//! For legacy support, you can enable the `wlr-data-control` feature to use the older
6//! `wlr-data-control-unstable-v1` protocol. This handles the clipboard on sway, hyperland or kde
7//! that implement the protocol.
8//! You can view the protocol in [wlr-data-control-unstable-v1](https://wayland.app/protocols/wlr-data-control-unstable-v1). Here we simply explain it.
9//!
10//! This protocol involves there register: WlSeat, ZwlrDataControlManagerV1,
11//! ZwlrDataControlDeviceV1, and zwlrDataControlOfferV1, seat is used to create a device, and the
12//! device will handle the copy and paste,
13//!
14//! when you want to use this protocol, you need to init these first, then enter the eventloop, you
15//! can view our code, part of `init()`
16//!
17//! ### Paste
18//! Copy is mainly in the part of device dispatch and dataoffer one, there are two road to finished
19//! a copy event, this is decided by the time you send the receive request of ZwlrDataControlDeviceV1;
20//!
21//! #### Road 1
22//!
23//! * 1. First, the event enter to DataOffer event of zwlrDataControlOfferV1, it will send a
24//!      zwlrDataControlOfferV1 object, this will include the data message of clipboard, if you send
25//!      this time, you will not know the mimetype. In this time, the data includes the text selected
26//!      and copied, here you can pass a file description to receive, and mimetype of TEXT, because at
27//!      this time you do not know any mimetype of the data
28//! * 2. It will enter the event of zwlrDataControlOfferV1, there the mimetype be send, but before
29//!      , you ignore the mimetype
30//! * 3. it enter the selection, follow the document of the protocol, you need to destroy the offer,
31//!      if there is one,
32//! * 4. The main loop is end, then you need to run roundtrip, again, for the pipeline finished,
33//!      then you will receive the text. Note, if in this routine, you need to check the mimetype in the
34//!      end, because the data in pipeline maybe not text
35//!
36//! ### Road 2
37//! it is similar with Road 1, but send receive request when receive selection event, this time you
38//! will receive mimetype. Here you can only receive the data which is by copy
39//!
40//! ### Copy
41//!
42//! Paste with wlr-data-control-unstable-v1, need data provider alive, you can make an experiment,
43//! if you copy a text from firefox, and kill firefox, you will find, you cannot paste! It is
44//! amazing, doesn't it? So the copy event need to keep alive if the data is still available. You
45//! will find that if you copy a text with wl-copy, it will always alive in htop, if you view the
46//! code, you will find it fork itself, and live in the backend, until you copy another text from
47//! other place, it will die.
48//!
49//! Then the problem is, how to copy the data, and when to kill the progress?
50//!
51//! Copy event involves ZwlrDataControlDeviceV1 and ZwlrDataControlSourceV1.
52//!
53//! * 1. if you want to send the data, you need to create a new ZwlrDataControlSourceV1, use the
54//!      create_data_source function of zwlr_data_control_manager_v1, create a new one, and set the
55//!      mimetype to it , use `offer` request. You can set multi times,
56//! * 2. start a never end loop of blocking_dispatch, but it is not never end loop, it should break
57//!      when receive cancelled event of ZwlrDataControlSourceV1, this means another data is copied, the
58//!      progress is not needed anymore
59//!   * 2.1. In blocking_dispatches at the beginning, you will receive some signals of send, with
60//!     mimetype and a file description, write the data to the fd, then copy will finished, data
61//!     will be in clipboard
62//!   * 2.2. when received cancelled, exit the progress
63//!
64//! A simple example to create a clipboard listener is following:
65//!
66//! ```rust, no_run
67//! use wayland_clipboard_listener::WlClipboardPasteStream;
68//! use wayland_clipboard_listener::WlListenType;
69//!
70//! fn main() {
71//!     let mut stream = WlClipboardPasteStream::init(WlListenType::ListenOnCopy).unwrap();
72//!     // Note: MIME type priority is ignored when WlListenType is ListenOnSelect
73//!     // stream.set_priority(vec![
74//!     //     "image/jpeg".into(),
75//!     //     "text/plain;charset=utf-8".into(),
76//!     // ]);
77//!     for context in stream.paste_stream().flatten() {
78//!         println!("{context:?}");
79//!     }
80//! }
81//!
82//! ```
83//!
84//! For using the legacy wlr protocol (with the `wlr-data-control` feature):
85//! ```rust, no_run
86//! # #[cfg(feature = "wlr-data-control")]
87//! # {
88//! use wayland_clipboard_listener::WlClipboardPasteStreamWlr;
89//! use wayland_clipboard_listener::WlListenType;
90//!
91//! fn main() {
92//!     let mut stream = WlClipboardPasteStreamWlr::init(WlListenType::ListenOnCopy).unwrap();
93//!     for context in stream.paste_stream().flatten() {
94//!         println!("{context:?}");
95//!     }
96//! }
97//! # }
98//! ```
99//!
100//! A simple example to create a wl-copy is following:
101//! ``` rust, no_run
102//! use wayland_clipboard_listener::{WlClipboardCopyStream, WlClipboardListenerError};
103//! fn main() -> Result<(), WlClipboardListenerError> {
104//!     let args = std::env::args();
105//!     if args.len() != 2 {
106//!         println!("You need to pass a string to it");
107//!         return Ok(());
108//!     }
109//!     let context: &str = &args.last().unwrap();
110//!     let mut stream = WlClipboardCopyStream::init()?;
111//!     stream.copy_to_clipboard(context.as_bytes().to_vec(), vec!["TEXT"] ,false)?;
112//!     Ok(())
113//! }
114
115#![allow(clippy::needless_doctest_main)]
116
117mod constvar;
118mod dispatch;
119
120#[cfg(feature = "wlr-data-control")]
121mod dispatch_wlr;
122
123use std::io::Read;
124
125use wayland_client::{protocol::wl_seat, Connection, DispatchError, EventQueue};
126
127use wayland_protocols::ext::data_control::v1::client::{
128    ext_data_control_device_v1, ext_data_control_manager_v1,
129};
130
131#[cfg(feature = "wlr-data-control")]
132use wayland_protocols_wlr::data_control::v1::client::{
133    zwlr_data_control_device_v1, zwlr_data_control_manager_v1,
134};
135
136use std::sync::{Arc, Mutex};
137
138use thiserror::Error;
139
140use constvar::{IMAGE, TEXT};
141
142/// listentype
143/// if ListenOnHover, it will be useful for translation apps, but in dispatch, we cannot know the
144/// mime_types, it can only handle text
145///
146/// ListenOnCopy will get the full mimetype, but you should copy to enable the listen,
147#[derive(Debug)]
148pub enum WlListenType {
149    ListenOnSelect,
150    ListenOnCopy,
151}
152
153/// Error
154/// it describe three kind of error
155/// 1. failed when init
156/// 2. failed in queue
157/// 3. failed in pipereader
158#[derive(Error, Debug)]
159pub enum WlClipboardListenerError {
160    #[error("Init Failed")]
161    InitFailed(String),
162    #[error("Error during queue")]
163    QueueError(String),
164    #[error("DispatchError")]
165    DispatchError(#[from] DispatchError),
166    #[error("PipeError")]
167    PipeError,
168}
169
170/// context
171/// here describe two types of context
172/// 1. text, just [String]
173/// 2. file , with [`Vec<u8>`]
174#[derive(Debug)]
175pub struct ClipBoardListenContext {
176    pub mime_type: String,
177    pub context: Vec<u8>,
178}
179
180#[derive(Debug)]
181pub struct ClipBoardListenMessage {
182    pub mime_types: Vec<String>,
183    pub context: ClipBoardListenContext,
184}
185
186/// Paste stream
187/// it is used to handle paste event
188pub struct WlClipboardPasteStream {
189    inner: WlClipboardListenerStream,
190}
191
192impl WlClipboardPasteStream {
193    /// init a paste steam, you can use WlListenType::ListenOnSelect to watch the select event
194    /// It can just listen on text
195    /// use ListenOnCopy will receive the mimetype, can copy many types
196    pub fn init(listentype: WlListenType) -> Result<Self, WlClipboardListenerError> {
197        Ok(Self {
198            inner: WlClipboardListenerStream::init(listentype)?,
199        })
200    }
201
202    /// return a steam, to iter
203    /// ```rust, no_run
204    /// use wayland_clipboard_listener::WlClipboardPasteStream;
205    /// use wayland_clipboard_listener::WlListenType;
206    ///
207    /// let mut stream = WlClipboardPasteStream::init(WlListenType::ListenOnCopy).unwrap();
208    ///
209    /// for context in stream.paste_stream().flatten() {
210    ///     println!("{context:?}")
211    /// }
212    /// ```
213    pub fn paste_stream(&mut self) -> &mut WlClipboardListenerStream {
214        &mut self.inner
215    }
216    ///  just get the clipboard once
217    pub fn get_clipboard(&mut self) -> Result<ClipBoardListenMessage, WlClipboardListenerError> {
218        self.inner.get_clipboard_sync()
219    }
220    ///  just get the clipboard once
221    pub fn try_get_clipboard(
222        &mut self,
223    ) -> Result<Option<ClipBoardListenMessage>, WlClipboardListenerError> {
224        self.inner.try_get_clipboard()
225    }
226
227    /// Set MIME type priority (only applies when using ListenOnCopy)
228    pub fn set_priority(&mut self, val: Vec<String>) {
229        self.inner.set_priority = Some(val);
230    }
231}
232
233/// copy stream,
234/// it can used to make a wl-copy
235pub struct WlClipboardCopyStream {
236    inner: WlClipboardListenerStream,
237}
238
239impl WlClipboardCopyStream {
240    /// init a copy steam, you can use it to copy some files
241    pub fn init() -> Result<Self, WlClipboardListenerError> {
242        Ok(Self {
243            inner: WlClipboardListenerStream::init(WlListenType::ListenOnCopy)?,
244        })
245    }
246
247    /// it will run a never end loop, to handle the paste event, like what wl-copy do
248    /// it will live until next copy event happened
249    /// you need to pass data and if use useprimary to it,
250    /// if is useprimary, you can use the middle button of mouse to paste
251    /// Take [primary-selection](https://patchwork.freedesktop.org/patch/257267/) as reference
252    /// ``` rust, no_run
253    /// use wayland_clipboard_listener::{WlClipboardCopyStream, WlClipboardListenerError};
254    /// let args = std::env::args();
255    /// if args.len() != 2 {
256    ///     println!("You need to pass a string to it");
257    /// } else {
258    ///     let context: &str = &args.last().unwrap();
259    ///     let mut stream = WlClipboardCopyStream::init().unwrap();
260    ///     stream.copy_to_clipboard(context.as_bytes().to_vec(), vec!["STRING"], false).unwrap();
261    /// }
262    ///```
263    pub fn copy_to_clipboard(
264        &mut self,
265        data: Vec<u8>,
266        mimetypes: Vec<&str>,
267        useprimary: bool,
268    ) -> Result<(), WlClipboardListenerError> {
269        self.inner.copy_to_clipboard(data, mimetypes, useprimary)
270    }
271}
272/// Stream, provide a iter to listen to clipboard
273/// Note, the iter will loop very fast, you would better to use thread sleep
274/// or iter you self
275pub struct WlClipboardListenerStream {
276    listentype: WlListenType,
277    seat: Option<wl_seat::WlSeat>,
278    seat_name: Option<String>,
279    data_manager: Option<ext_data_control_manager_v1::ExtDataControlManagerV1>,
280    data_device: Option<ext_data_control_device_v1::ExtDataControlDeviceV1>,
281    mime_types: Vec<String>,
282    set_priority: Option<Vec<String>>,
283    pipereader: Option<os_pipe::PipeReader>,
284    current_type: Option<String>,
285    queue: Option<Arc<Mutex<EventQueue<Self>>>>,
286    copy_data: Option<Vec<u8>>,
287    copy_cancelled: bool,
288}
289
290impl Iterator for WlClipboardListenerStream {
291    type Item = Result<ClipBoardListenMessage, WlClipboardListenerError>;
292
293    fn next(&mut self) -> Option<Self::Item> {
294        let data = self.get_clipboard_sync();
295        if let Err(WlClipboardListenerError::DispatchError(err)) = data.as_ref() {
296            panic!("error with wayland side: {err}");
297        }
298        Some(data)
299    }
300}
301
302impl WlClipboardListenerStream {
303    /// private init
304    /// to init a stream
305    fn init(listentype: WlListenType) -> Result<Self, WlClipboardListenerError> {
306        let conn = Connection::connect_to_env().map_err(|_| {
307            WlClipboardListenerError::InitFailed("Cannot connect to wayland".to_string())
308        })?;
309
310        let mut event_queue = conn.new_event_queue();
311        let qhandle = event_queue.handle();
312
313        let display = conn.display();
314
315        display.get_registry(&qhandle, ());
316        let mut state = WlClipboardListenerStream {
317            listentype,
318            seat: None,
319            seat_name: None,
320            data_manager: None,
321            data_device: None,
322            mime_types: Vec::new(),
323            set_priority: None,
324            pipereader: None,
325            current_type: None,
326            queue: None,
327            copy_data: None,
328            copy_cancelled: false,
329        };
330
331        event_queue.blocking_dispatch(&mut state).map_err(|e| {
332            WlClipboardListenerError::InitFailed(format!("Initial dispatch failed: {e}"))
333        })?;
334
335        if !state.device_ready() {
336            return Err(WlClipboardListenerError::InitFailed(
337                "Cannot get seat and data manager".to_string(),
338            ));
339        }
340
341        while state.seat_name.is_none() {
342            event_queue.roundtrip(&mut state).map_err(|_| {
343                WlClipboardListenerError::InitFailed("Cannot roundtrip during init".to_string())
344            })?;
345        }
346
347        state.set_data_device(&qhandle);
348        state.queue = Some(Arc::new(Mutex::new(event_queue)));
349        Ok(state)
350    }
351
352    /// copy data to stream
353    /// pass [Vec<u8>] as data
354    /// now it can just copy text
355    /// It will always live in the background, so you need to handle it yourself
356    fn copy_to_clipboard(
357        &mut self,
358        data: Vec<u8>,
359        mimetypes: Vec<&str>,
360        useprimary: bool,
361    ) -> Result<(), WlClipboardListenerError> {
362        let eventqh = self.queue.clone().unwrap();
363        let mut event_queue = eventqh.lock().unwrap();
364        let qh = event_queue.handle();
365        let manager = self.data_manager.as_ref().unwrap();
366        let source = manager.create_data_source(&qh, ());
367        let device = self.data_device.as_ref().unwrap();
368
369        for mimetype in mimetypes {
370            source.offer(mimetype.to_string());
371        }
372
373        if useprimary {
374            device.set_primary_selection(Some(&source));
375        } else {
376            device.set_selection(Some(&source));
377        }
378
379        self.copy_data = Some(data);
380        while !self.copy_cancelled {
381            event_queue
382                .blocking_dispatch(self)
383                .map_err(|e| WlClipboardListenerError::QueueError(e.to_string()))?;
384        }
385        self.copy_data = None;
386        self.copy_cancelled = false;
387        Ok(())
388    }
389
390    /// get data from clipboard for once
391    /// it is also used in iter
392    fn get_clipboard_sync(&mut self) -> Result<ClipBoardListenMessage, WlClipboardListenerError> {
393        // get queue, start blocking_dispatch for first loop
394        let queue = self.queue.clone().unwrap();
395        let mut queue = queue
396            .lock()
397            .map_err(|e| WlClipboardListenerError::QueueError(e.to_string()))?;
398        while self.pipereader.is_none() {
399            queue.blocking_dispatch(self)?;
400        }
401
402        // roundtrip to init pipereader
403        queue.roundtrip(self)?;
404        let mut read = self.pipereader.as_ref().unwrap();
405        let mut context = vec![];
406        read.read_to_end(&mut context)
407            .map_err(|_| WlClipboardListenerError::PipeError)?;
408        self.pipereader = None;
409        let mime_types = self.mime_types.clone();
410        self.mime_types.clear();
411        let mime_type = self.current_type.clone().unwrap();
412        Ok(ClipBoardListenMessage {
413            mime_types,
414            context: ClipBoardListenContext { mime_type, context },
415        })
416    }
417
418    /// get data from clipboard for once
419    /// it is also used in iter
420    fn try_get_clipboard(
421        &mut self,
422    ) -> Result<Option<ClipBoardListenMessage>, WlClipboardListenerError> {
423        // get queue, start blocking_dispatch for first loop
424        let queue = self.queue.clone().unwrap();
425        let mut queue = queue
426            .lock()
427            .map_err(|e| WlClipboardListenerError::QueueError(e.to_string()))?;
428        queue.blocking_dispatch(self)?;
429        if self.pipereader.is_some() {
430            // roundtrip to init pipereader
431            queue.roundtrip(self)?;
432            let mut read = self.pipereader.as_ref().unwrap();
433            let mut context = vec![];
434            read.read_to_end(&mut context)
435                .map_err(|_| WlClipboardListenerError::PipeError)?;
436            self.pipereader = None;
437            let mime_types = self.mime_types.clone();
438            self.mime_types.clear();
439            let mime_type = self.current_type.clone().unwrap();
440            Ok(Some(ClipBoardListenMessage {
441                mime_types,
442                context: ClipBoardListenContext { mime_type, context },
443            }))
444        } else {
445            Ok(None)
446        }
447    }
448
449    fn device_ready(&self) -> bool {
450        self.seat.is_some() && self.data_manager.is_some()
451    }
452
453    fn set_data_device(&mut self, qh: &wayland_client::QueueHandle<Self>) {
454        let seat = self.seat.as_ref().unwrap();
455        let manager = self.data_manager.as_ref().unwrap();
456        let device = manager.get_data_device(seat, qh, ());
457
458        self.data_device = Some(device);
459    }
460
461    fn is_text(&self) -> bool {
462        !self.mime_types.is_empty()
463            && self.mime_types.contains(&TEXT.to_string())
464            && !self.mime_types.contains(&IMAGE.to_string())
465    }
466}
467
468// Wlr protocol support (legacy)
469#[cfg(feature = "wlr-data-control")]
470pub struct WlClipboardPasteStreamWlr {
471    inner: WlClipboardListenerStreamWlr,
472}
473
474#[cfg(feature = "wlr-data-control")]
475impl WlClipboardPasteStreamWlr {
476    /// init a paste steam, you can use WlListenType::ListenOnSelect to watch the select event
477    /// It can just listen on text
478    /// use ListenOnCopy will receive the mimetype, can copy many types
479    pub fn init(listentype: WlListenType) -> Result<Self, WlClipboardListenerError> {
480        Ok(Self {
481            inner: WlClipboardListenerStreamWlr::init(listentype)?,
482        })
483    }
484
485    /// return a steam, to iter
486    /// ```rust, no_run
487    /// use wayland_clipboard_listener::WlClipboardPasteStreamWlr;
488    /// use wayland_clipboard_listener::WlListenType;
489    ///
490    /// let mut stream = WlClipboardPasteStreamWlr::init(WlListenType::ListenOnCopy).unwrap();
491    ///
492    /// for context in stream.paste_stream().flatten() {
493    ///     println!("{context:?}")
494    /// }
495    /// ```
496    pub fn paste_stream(&mut self) -> &mut WlClipboardListenerStreamWlr {
497        &mut self.inner
498    }
499    ///  just get the clipboard once
500    pub fn get_clipboard(&mut self) -> Result<ClipBoardListenMessage, WlClipboardListenerError> {
501        self.inner.get_clipboard_sync()
502    }
503    ///  just get the clipboard once
504    pub fn try_get_clipboard(
505        &mut self,
506    ) -> Result<Option<ClipBoardListenMessage>, WlClipboardListenerError> {
507        self.inner.try_get_clipboard()
508    }
509
510    /// Set MIME type priority (only applies when using ListenOnCopy)
511    pub fn set_priority(&mut self, val: Vec<String>) {
512        self.inner.set_priority = Some(val);
513    }
514}
515
516/// copy stream,
517/// it can used to make a wl-copy
518#[cfg(feature = "wlr-data-control")]
519pub struct WlClipboardCopyStreamWlr {
520    inner: WlClipboardListenerStreamWlr,
521}
522
523#[cfg(feature = "wlr-data-control")]
524impl WlClipboardCopyStreamWlr {
525    /// init a copy steam, you can use it to copy some files
526    pub fn init() -> Result<Self, WlClipboardListenerError> {
527        Ok(Self {
528            inner: WlClipboardListenerStreamWlr::init(WlListenType::ListenOnCopy)?,
529        })
530    }
531
532    /// it will run a never end loop, to handle the paste event, like what wl-copy do
533    /// it will live until next copy event happened
534    /// you need to pass data and if use useprimary to it,
535    /// if is useprimary, you can use the middle button of mouse to paste
536    /// Take [primary-selection](https://patchwork.freedesktop.org/patch/257267/) as reference
537    /// ``` rust, no_run
538    /// use wayland_clipboard_listener::{WlClipboardCopyStreamWlr, WlClipboardListenerError};
539    /// let args = std::env::args();
540    /// if args.len() != 2 {
541    ///     println!("You need to pass a string to it");
542    /// } else {
543    ///     let context: &str = &args.last().unwrap();
544    ///     let mut stream = WlClipboardCopyStreamWlr::init().unwrap();
545    ///     stream.copy_to_clipboard(context.as_bytes().to_vec(), vec!["STRING"], false).unwrap();
546    /// }
547    ///```
548    pub fn copy_to_clipboard(
549        &mut self,
550        data: Vec<u8>,
551        mimetypes: Vec<&str>,
552        useprimary: bool,
553    ) -> Result<(), WlClipboardListenerError> {
554        self.inner.copy_to_clipboard(data, mimetypes, useprimary)
555    }
556}
557
558/// Stream, provide a iter to listen to clipboard
559/// Note, the iter will loop very fast, you would better to use thread sleep
560/// or iter you self
561#[cfg(feature = "wlr-data-control")]
562pub struct WlClipboardListenerStreamWlr {
563    listentype: WlListenType,
564    seat: Option<wl_seat::WlSeat>,
565    seat_name: Option<String>,
566    data_manager: Option<zwlr_data_control_manager_v1::ZwlrDataControlManagerV1>,
567    data_device: Option<zwlr_data_control_device_v1::ZwlrDataControlDeviceV1>,
568    mime_types: Vec<String>,
569    set_priority: Option<Vec<String>>,
570    pipereader: Option<os_pipe::PipeReader>,
571    current_type: Option<String>,
572    queue: Option<Arc<Mutex<EventQueue<Self>>>>,
573    copy_data: Option<Vec<u8>>,
574    copy_cancelled: bool,
575}
576
577#[cfg(feature = "wlr-data-control")]
578impl Iterator for WlClipboardListenerStreamWlr {
579    type Item = Result<ClipBoardListenMessage, WlClipboardListenerError>;
580
581    fn next(&mut self) -> Option<Self::Item> {
582        let data = self.get_clipboard_sync();
583        if let Err(WlClipboardListenerError::DispatchError(err)) = data.as_ref() {
584            panic!("error with wayland side: {err}");
585        }
586        Some(data)
587    }
588}
589
590#[cfg(feature = "wlr-data-control")]
591impl WlClipboardListenerStreamWlr {
592    /// private init
593    /// to init a stream
594    fn init(listentype: WlListenType) -> Result<Self, WlClipboardListenerError> {
595        let conn = Connection::connect_to_env().map_err(|_| {
596            WlClipboardListenerError::InitFailed("Cannot connect to wayland".to_string())
597        })?;
598
599        let mut event_queue = conn.new_event_queue();
600        let qhandle = event_queue.handle();
601
602        let display = conn.display();
603
604        display.get_registry(&qhandle, ());
605        let mut state = WlClipboardListenerStreamWlr {
606            listentype,
607            seat: None,
608            seat_name: None,
609            data_manager: None,
610            data_device: None,
611            mime_types: Vec::new(),
612            set_priority: None,
613            pipereader: None,
614            current_type: None,
615            queue: None,
616            copy_data: None,
617            copy_cancelled: false,
618        };
619
620        event_queue.blocking_dispatch(&mut state).map_err(|e| {
621            WlClipboardListenerError::InitFailed(format!("Initial dispatch failed: {e}"))
622        })?;
623
624        if !state.device_ready() {
625            return Err(WlClipboardListenerError::InitFailed(
626                "Cannot get seat and data manager".to_string(),
627            ));
628        }
629
630        while state.seat_name.is_none() {
631            event_queue.roundtrip(&mut state).map_err(|_| {
632                WlClipboardListenerError::InitFailed("Cannot roundtrip during init".to_string())
633            })?;
634        }
635
636        state.set_data_device(&qhandle);
637        state.queue = Some(Arc::new(Mutex::new(event_queue)));
638        Ok(state)
639    }
640
641    /// copy data to stream
642    /// pass [Vec<u8>] as data
643    /// now it can just copy text
644    /// It will always live in the background, so you need to handle it yourself
645    fn copy_to_clipboard(
646        &mut self,
647        data: Vec<u8>,
648        mimetypes: Vec<&str>,
649        useprimary: bool,
650    ) -> Result<(), WlClipboardListenerError> {
651        let eventqh = self.queue.clone().unwrap();
652        let mut event_queue = eventqh.lock().unwrap();
653        let qh = event_queue.handle();
654        let manager = self.data_manager.as_ref().unwrap();
655        let source = manager.create_data_source(&qh, ());
656        let device = self.data_device.as_ref().unwrap();
657
658        for mimetype in mimetypes {
659            source.offer(mimetype.to_string());
660        }
661
662        if useprimary {
663            device.set_primary_selection(Some(&source));
664        } else {
665            device.set_selection(Some(&source));
666        }
667
668        self.copy_data = Some(data);
669        while !self.copy_cancelled {
670            event_queue
671                .blocking_dispatch(self)
672                .map_err(|e| WlClipboardListenerError::QueueError(e.to_string()))?;
673        }
674        self.copy_data = None;
675        self.copy_cancelled = false;
676        Ok(())
677    }
678
679    /// get data from clipboard for once
680    /// it is also used in iter
681    fn get_clipboard_sync(&mut self) -> Result<ClipBoardListenMessage, WlClipboardListenerError> {
682        // get queue, start blocking_dispatch for first loop
683        let queue = self.queue.clone().unwrap();
684        let mut queue = queue
685            .lock()
686            .map_err(|e| WlClipboardListenerError::QueueError(e.to_string()))?;
687        while self.pipereader.is_none() {
688            queue.blocking_dispatch(self)?;
689        }
690
691        // roundtrip to init pipereader
692        queue.roundtrip(self)?;
693        let mut read = self.pipereader.as_ref().unwrap();
694        let mut context = vec![];
695        read.read_to_end(&mut context)
696            .map_err(|_| WlClipboardListenerError::PipeError)?;
697        self.pipereader = None;
698        let mime_types = self.mime_types.clone();
699        self.mime_types.clear();
700        let mime_type = self.current_type.clone().unwrap();
701        Ok(ClipBoardListenMessage {
702            mime_types,
703            context: ClipBoardListenContext { mime_type, context },
704        })
705    }
706
707    /// get data from clipboard for once
708    /// it is also used in iter
709    fn try_get_clipboard(
710        &mut self,
711    ) -> Result<Option<ClipBoardListenMessage>, WlClipboardListenerError> {
712        // get queue, start blocking_dispatch for first loop
713        let queue = self.queue.clone().unwrap();
714        let mut queue = queue
715            .lock()
716            .map_err(|e| WlClipboardListenerError::QueueError(e.to_string()))?;
717        queue.blocking_dispatch(self)?;
718        if self.pipereader.is_some() {
719            // roundtrip to init pipereader
720            queue.roundtrip(self)?;
721            let mut read = self.pipereader.as_ref().unwrap();
722            let mut context = vec![];
723            read.read_to_end(&mut context)
724                .map_err(|_| WlClipboardListenerError::PipeError)?;
725            self.pipereader = None;
726            let mime_types = self.mime_types.clone();
727            self.mime_types.clear();
728            let mime_type = self.current_type.clone().unwrap();
729            Ok(Some(ClipBoardListenMessage {
730                mime_types,
731                context: ClipBoardListenContext { mime_type, context },
732            }))
733        } else {
734            Ok(None)
735        }
736    }
737
738    fn device_ready(&self) -> bool {
739        self.seat.is_some() && self.data_manager.is_some()
740    }
741
742    fn set_data_device(&mut self, qh: &wayland_client::QueueHandle<Self>) {
743        let seat = self.seat.as_ref().unwrap();
744        let manager = self.data_manager.as_ref().unwrap();
745        let device = manager.get_data_device(seat, qh, ());
746
747        self.data_device = Some(device);
748    }
749
750    fn is_text(&self) -> bool {
751        !self.mime_types.is_empty()
752            && self.mime_types.contains(&TEXT.to_string())
753            && !self.mime_types.contains(&IMAGE.to_string())
754    }
755}