Skip to main content

wl_clipboard_rs/
copy.rs

1//! Copying and clearing clipboard contents.
2
3use std::collections::hash_map::Entry;
4use std::collections::HashMap;
5use std::ffi::OsString;
6use std::fs::File;
7use std::io::{self, Cursor};
8use std::sync::mpsc::sync_channel;
9use std::sync::Arc;
10use std::{iter, thread};
11
12use rustix::fs::{fcntl_setfl, OFlags};
13use wayland_client::globals::GlobalListContents;
14use wayland_client::protocol::wl_registry::WlRegistry;
15use wayland_client::protocol::wl_seat::WlSeat;
16use wayland_client::{
17    delegate_dispatch, event_created_child, ConnectError, Dispatch, DispatchError, EventQueue,
18};
19
20use crate::common::{self, initialize};
21use crate::data_control::{
22    self, impl_dispatch_device, impl_dispatch_manager, impl_dispatch_offer, impl_dispatch_source,
23};
24use crate::seat_data::SeatData;
25use crate::utils::{is_text, PASSWORD_MANAGER_HINT_MIME_TYPE};
26
27const TEXT_PLAIN_MIME: &str = "text/plain";
28
29/// The clipboard to operate on.
30#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord, Default)]
31#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
32pub enum ClipboardType {
33    /// The regular clipboard.
34    #[default]
35    Regular,
36    /// The "primary" clipboard.
37    ///
38    /// Working with the "primary" clipboard requires the compositor to support ext-data-control,
39    /// or wlr-data-control version 2 or above.
40    Primary,
41    /// Operate on both clipboards at once.
42    ///
43    /// Useful for atomically setting both clipboards at once. This option requires the "primary"
44    /// clipboard to be supported.
45    Both,
46}
47
48/// MIME type to offer the copied data under.
49#[derive(Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)]
50#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
51pub enum MimeType {
52    /// Detect the MIME type automatically from the data.
53    #[cfg_attr(test, proptest(skip))]
54    Autodetect,
55    /// Offer a number of common plain text MIME types.
56    Text,
57    /// Offer a specific MIME type.
58    Specific(String),
59}
60
61/// Source for copying.
62#[derive(Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)]
63#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
64pub enum Source {
65    /// Copy contents of the standard input.
66    #[cfg_attr(test, proptest(skip))]
67    StdIn,
68    /// Copy the given bytes.
69    Bytes(Box<[u8]>),
70}
71
72/// Source for copying, with a MIME type.
73///
74/// Used for [`copy_multi`].
75///
76/// [`copy_multi`]: fn.copy_multi.html
77#[derive(Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)]
78pub struct MimeSource {
79    pub source: Source,
80    pub mime_type: MimeType,
81}
82
83/// Seat to operate on.
84#[derive(Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord, Default)]
85pub enum Seat {
86    /// Operate on all existing seats at once.
87    #[default]
88    All,
89    /// Operate on a seat with the given name.
90    Specific(String),
91}
92
93/// Number of paste requests to serve.
94#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord, Default)]
95pub enum ServeRequests {
96    /// Serve requests indefinitely.
97    #[default]
98    Unlimited,
99    /// Serve only the given number of requests.
100    Only(usize),
101}
102
103/// Options and flags that are used to customize the copying.
104#[derive(Clone, Eq, PartialEq, Debug, Default, Hash, PartialOrd, Ord)]
105pub struct Options {
106    /// The clipboard to work with.
107    clipboard: ClipboardType,
108
109    /// The seat to work with.
110    seat: Seat,
111
112    /// Trim the trailing newline character before copying.
113    ///
114    /// This flag is only applied for text MIME types.
115    trim_newline: bool,
116
117    /// Do not spawn a separate thread for serving copy requests.
118    ///
119    /// Setting this flag will result in the call to `copy()` **blocking** until all data sources
120    /// it creates are destroyed, e.g. until someone else copies something into the clipboard.
121    foreground: bool,
122
123    /// Number of paste requests to serve.
124    ///
125    /// Limiting the number of paste requests to one effectively clears the clipboard after the
126    /// first paste. It can be used when copying e.g. sensitive data, like passwords. Note however
127    /// that certain apps may have issues pasting when this option is used, in particular XWayland
128    /// clients are known to suffer from this.
129    ///
130    /// Requests for the [password manager hint][crate::utils::PASSWORD_MANAGER_HINT_MIME_TYPE]
131    /// are not counted toward this limit.
132    serve_requests: ServeRequests,
133
134    /// Hint that the copied data contains passwords, keys, or other sensitive content.
135    ///
136    /// Some clipboard managers may react by not persisting the copied data in clipboard history.
137    sensitive: bool,
138
139    /// Omit additional text mime types which are offered by default if at least one text mime type is provided.
140    ///
141    /// Omits additionally offered `text/plain;charset=utf-8`, `text/plain`, `STRING`, `UTF8_STRING` and
142    /// `TEXT` mime types which are offered by default if at least one text mime type is provided.
143    omit_additional_text_mime_types: bool,
144}
145
146/// A copy operation ready to start serving requests.
147pub struct PreparedCopy {
148    queue: EventQueue<State>,
149    state: State,
150    sources: Vec<data_control::Source>,
151}
152
153/// Errors that can occur for copying the source data to a temporary file.
154#[derive(thiserror::Error, Debug)]
155pub enum SourceCreationError {
156    #[error("Couldn't create a temporary directory")]
157    TempDirCreate(#[source] io::Error),
158
159    #[error("Couldn't create a temporary file")]
160    TempFileCreate(#[source] io::Error),
161
162    #[error("Couldn't copy data to the temporary file")]
163    DataCopy(#[source] io::Error),
164
165    #[error("Couldn't write to the temporary file")]
166    TempFileWrite(#[source] io::Error),
167
168    #[error("Couldn't open the temporary file for newline trimming")]
169    TempFileOpen(#[source] io::Error),
170
171    #[error("Couldn't get the temporary file metadata for newline trimming")]
172    TempFileMetadata(#[source] io::Error),
173
174    #[error("Couldn't seek the temporary file for newline trimming")]
175    TempFileSeek(#[source] io::Error),
176
177    #[error("Couldn't read the last byte of the temporary file for newline trimming")]
178    TempFileRead(#[source] io::Error),
179
180    #[error("Couldn't truncate the temporary file for newline trimming")]
181    TempFileTruncate(#[source] io::Error),
182}
183
184/// Errors that can occur for copying and clearing the clipboard.
185#[derive(thiserror::Error, Debug)]
186pub enum Error {
187    #[error("There are no seats")]
188    NoSeats,
189
190    #[error("Couldn't open the provided Wayland socket")]
191    SocketOpenError(#[source] io::Error),
192
193    #[error("Couldn't connect to the Wayland compositor")]
194    WaylandConnection(#[source] ConnectError),
195
196    #[error("Wayland compositor communication error")]
197    WaylandCommunication(#[source] DispatchError),
198
199    #[error(
200        "A required Wayland protocol ({} version {}) is not supported by the compositor",
201        name,
202        version
203    )]
204    MissingProtocol { name: &'static str, version: u32 },
205
206    #[error("The compositor does not support primary selection")]
207    PrimarySelectionUnsupported,
208
209    #[error("The requested seat was not found")]
210    SeatNotFound,
211
212    #[error("Error copying the source into a temporary file")]
213    TempCopy(#[source] SourceCreationError),
214
215    #[error("Couldn't remove the temporary file")]
216    TempFileRemove(#[source] io::Error),
217
218    #[error("Couldn't remove the temporary directory")]
219    TempDirRemove(#[source] io::Error),
220
221    #[error("Error satisfying a paste request")]
222    Paste(#[source] DataSourceError),
223}
224
225impl From<common::Error> for Error {
226    fn from(x: common::Error) -> Self {
227        use common::Error::*;
228
229        match x {
230            SocketOpenError(err) => Error::SocketOpenError(err),
231            WaylandConnection(err) => Error::WaylandConnection(err),
232            WaylandCommunication(err) => Error::WaylandCommunication(err.into()),
233            MissingProtocol { name, version } => Error::MissingProtocol { name, version },
234        }
235    }
236}
237
238#[derive(thiserror::Error, Debug)]
239pub enum DataSourceError {
240    #[error("Couldn't open the data file")]
241    FileOpen(#[source] io::Error),
242
243    #[error("Couldn't copy the data to the target file descriptor")]
244    Copy(#[source] io::Error),
245}
246
247/// An inner source of data in-memory.
248///
249/// This is always cheap to clone.
250#[derive(Clone)]
251struct DataSourceStorage(Arc<[u8]>);
252
253struct DataSource {
254    mime_type: String,
255    source: DataSourceStorage,
256}
257
258struct State {
259    common: common::State,
260    got_primary_selection: bool,
261    // This bool can be set to true when serving a request: either if an error occurs, or if the
262    // number of requests to serve was limited and the last request was served.
263    should_quit: bool,
264    data_sources: HashMap<String, DataSourceStorage>,
265    serve_requests: ServeRequests,
266    // An error that occurred while serving a request, if any.
267    error: Option<DataSourceError>,
268}
269
270delegate_dispatch!(State: [WlSeat: ()] => common::State);
271
272impl AsMut<common::State> for State {
273    fn as_mut(&mut self) -> &mut common::State {
274        &mut self.common
275    }
276}
277
278impl Dispatch<WlRegistry, GlobalListContents> for State {
279    fn event(
280        _state: &mut Self,
281        _proxy: &WlRegistry,
282        _event: <WlRegistry as wayland_client::Proxy>::Event,
283        _data: &GlobalListContents,
284        _conn: &wayland_client::Connection,
285        _qhandle: &wayland_client::QueueHandle<Self>,
286    ) {
287    }
288}
289
290impl_dispatch_manager!(State);
291
292impl_dispatch_device!(State, WlSeat, |state: &mut Self, event, seat| {
293    match event {
294        Event::DataOffer { id } => id.destroy(),
295        Event::Finished => {
296            state.common.seats.get_mut(seat).unwrap().set_device(None);
297        }
298        Event::PrimarySelection { .. } => {
299            state.got_primary_selection = true;
300        }
301        _ => (),
302    }
303});
304
305impl_dispatch_offer!(State);
306
307impl_dispatch_source!(State, |state: &mut Self,
308                              source: data_control::Source,
309                              event| {
310    match event {
311        Event::Send { mime_type, fd } => {
312            // Check if some other source already handled a paste request and indicated that we should
313            // quit.
314            if state.should_quit {
315                source.destroy();
316                return;
317            }
318
319            // I'm not sure if it's the compositor's responsibility to check that the mime type is
320            // valid. Let's check here just in case.
321            let data_source = match state.data_sources.get_mut(&mime_type) {
322                Some(source) => source,
323                None => {
324                    return;
325                }
326            };
327
328            let copy_result = || {
329                // Clear O_NONBLOCK, otherwise io::copy() will stop halfway.
330                fcntl_setfl(&fd, OFlags::empty()).map_err(io::Error::from)?;
331                let mut target_file = File::from(fd);
332
333                let mut source_content = Cursor::new(&data_source.0);
334                io::copy(&mut source_content, &mut target_file).map(drop)
335            };
336
337            // EPIPE means the destination closed the pipe early, which is valid
338            // behavior (e.g. the pasting program only read as much as it needed).
339            match copy_result() {
340                Ok(()) => {}
341                Err(e) if e.kind() == io::ErrorKind::BrokenPipe => {}
342                Err(e) => state.error = Some(DataSourceError::Copy(e)),
343            }
344
345            // Reading the password-manager hint doesn't count toward serve_requests.
346            let done = if mime_type == PASSWORD_MANAGER_HINT_MIME_TYPE {
347                false
348            } else if let ServeRequests::Only(left) = state.serve_requests {
349                let left = left.checked_sub(1).unwrap();
350                state.serve_requests = ServeRequests::Only(left);
351                left == 0
352            } else {
353                false
354            };
355
356            if done || state.error.is_some() {
357                state.should_quit = true;
358                source.destroy();
359            }
360        }
361        Event::Cancelled => source.destroy(),
362        _ => (),
363    }
364});
365
366impl Options {
367    /// Creates a blank new set of options ready for configuration.
368    #[inline]
369    pub fn new() -> Self {
370        Self::default()
371    }
372
373    /// Sets the clipboard to work with.
374    #[inline]
375    pub fn clipboard(&mut self, clipboard: ClipboardType) -> &mut Self {
376        self.clipboard = clipboard;
377        self
378    }
379
380    /// Sets the seat to use for copying.
381    #[inline]
382    pub fn seat(&mut self, seat: Seat) -> &mut Self {
383        self.seat = seat;
384        self
385    }
386
387    /// Sets the flag for trimming the trailing newline.
388    ///
389    /// This flag is only applied for text MIME types.
390    #[inline]
391    pub fn trim_newline(&mut self, trim_newline: bool) -> &mut Self {
392        self.trim_newline = trim_newline;
393        self
394    }
395
396    /// Sets the flag for not spawning a separate thread for serving copy requests.
397    ///
398    /// Setting this flag will result in the call to `copy()` **blocking** until all data sources
399    /// it creates are destroyed, e.g. until someone else copies something into the clipboard.
400    #[inline]
401    pub fn foreground(&mut self, foreground: bool) -> &mut Self {
402        self.foreground = foreground;
403        self
404    }
405
406    /// Sets the number of requests to serve.
407    ///
408    /// Limiting the number of requests to one effectively clears the clipboard after the first
409    /// paste. It can be used when copying e.g. sensitive data, like passwords. Note however that
410    /// certain apps may have issues pasting when this option is used, in particular XWayland
411    /// clients are known to suffer from this.
412    ///
413    /// Requests for the [password manager hint][crate::utils::PASSWORD_MANAGER_HINT_MIME_TYPE]
414    /// are not counted toward this limit.
415    #[inline]
416    pub fn serve_requests(&mut self, serve_requests: ServeRequests) -> &mut Self {
417        self.serve_requests = serve_requests;
418        self
419    }
420
421    /// Sets the flag for hinting that the copied data contains passwords, keys, or other sensitive content.
422    ///
423    /// Some clipboard managers may react by not persisting the copied data in clipboard history.
424    ///
425    /// Offers [`x-kde-passwordManagerHint`][crate::utils::PASSWORD_MANAGER_HINT_MIME_TYPE] with the contents `secret`, unless that MIME
426    /// type was supplied explicitly.
427    #[inline]
428    pub fn sensitive(&mut self, sensitive: bool) -> &mut Self {
429        self.sensitive = sensitive;
430        self
431    }
432
433    /// Sets the flag for omitting additional text mime types which are offered by default if at least one text mime type is provided.
434    ///
435    /// Omits additionally offered `text/plain;charset=utf-8`, `text/plain`, `STRING`, `UTF8_STRING` and
436    /// `TEXT` mime types which are offered by default if at least one text mime type is provided.
437    #[inline]
438    pub fn omit_additional_text_mime_types(
439        &mut self,
440        omit_additional_text_mime_types: bool,
441    ) -> &mut Self {
442        self.omit_additional_text_mime_types = omit_additional_text_mime_types;
443        self
444    }
445
446    /// Invokes the copy operation. See `copy()`.
447    ///
448    /// # Examples
449    ///
450    /// ```no_run
451    /// # extern crate wl_clipboard_rs;
452    /// # use wl_clipboard_rs::copy::Error;
453    /// # fn foo() -> Result<(), Error> {
454    /// use wl_clipboard_rs::copy::{MimeType, Options, Source};
455    ///
456    /// let opts = Options::new();
457    /// opts.copy(Source::Bytes([1, 2, 3][..].into()), MimeType::Autodetect)?;
458    /// # Ok(())
459    /// # }
460    /// ```
461    #[inline]
462    pub fn copy(self, source: Source, mime_type: MimeType) -> Result<(), Error> {
463        copy(self, source, mime_type)
464    }
465
466    /// Invokes the copy_multi operation. See `copy_multi()`.
467    ///
468    /// # Examples
469    ///
470    /// ```no_run
471    /// # extern crate wl_clipboard_rs;
472    /// # use wl_clipboard_rs::copy::Error;
473    /// # fn foo() -> Result<(), Error> {
474    /// use wl_clipboard_rs::copy::{MimeSource, MimeType, Options, Source};
475    ///
476    /// let opts = Options::new();
477    /// opts.copy_multi(vec![
478    ///     MimeSource {
479    ///         source: Source::Bytes([1, 2, 3][..].into()),
480    ///         mime_type: MimeType::Autodetect,
481    ///     },
482    ///     MimeSource {
483    ///         source: Source::Bytes([7, 8, 9][..].into()),
484    ///         mime_type: MimeType::Text,
485    ///     },
486    /// ])?;
487    /// # Ok(())
488    /// # }
489    /// ```
490    #[inline]
491    pub fn copy_multi(self, sources: Vec<MimeSource>) -> Result<(), Error> {
492        copy_multi(self, sources)
493    }
494
495    /// Invokes the prepare_copy operation. See `prepare_copy()`.
496    ///
497    /// # Panics
498    ///
499    /// Panics if `foreground` is `false`.
500    ///
501    /// # Examples
502    ///
503    /// ```no_run
504    /// # extern crate wl_clipboard_rs;
505    /// # use wl_clipboard_rs::copy::Error;
506    /// # fn foo() -> Result<(), Error> {
507    /// use wl_clipboard_rs::copy::{MimeSource, MimeType, Options, Source};
508    ///
509    /// let mut opts = Options::new();
510    /// opts.foreground(true);
511    /// let prepared_copy =
512    ///     opts.prepare_copy(Source::Bytes([1, 2, 3][..].into()), MimeType::Autodetect)?;
513    /// prepared_copy.serve()?;
514    ///
515    /// # Ok(())
516    /// # }
517    /// ```
518    #[inline]
519    pub fn prepare_copy(self, source: Source, mime_type: MimeType) -> Result<PreparedCopy, Error> {
520        prepare_copy(self, source, mime_type)
521    }
522
523    /// Invokes the prepare_copy_multi operation. See `prepare_copy_multi()`.
524    ///
525    /// # Panics
526    ///
527    /// Panics if `foreground` is `false`.
528    ///
529    /// # Examples
530    ///
531    /// ```no_run
532    /// # extern crate wl_clipboard_rs;
533    /// # use wl_clipboard_rs::copy::Error;
534    /// # fn foo() -> Result<(), Error> {
535    /// use wl_clipboard_rs::copy::{MimeSource, MimeType, Options, Source};
536    ///
537    /// let mut opts = Options::new();
538    /// opts.foreground(true);
539    /// let prepared_copy = opts.prepare_copy_multi(vec![
540    ///     MimeSource {
541    ///         source: Source::Bytes([1, 2, 3][..].into()),
542    ///         mime_type: MimeType::Autodetect,
543    ///     },
544    ///     MimeSource {
545    ///         source: Source::Bytes([7, 8, 9][..].into()),
546    ///         mime_type: MimeType::Text,
547    ///     },
548    /// ])?;
549    /// prepared_copy.serve()?;
550    ///
551    /// # Ok(())
552    /// # }
553    /// ```
554    #[inline]
555    pub fn prepare_copy_multi(self, sources: Vec<MimeSource>) -> Result<PreparedCopy, Error> {
556        prepare_copy_multi(self, sources)
557    }
558}
559
560impl PreparedCopy {
561    /// Starts serving copy requests.
562    ///
563    /// This function **blocks** until all requests are served or the clipboard is taken over by
564    /// some other application.
565    pub fn serve(mut self) -> Result<(), Error> {
566        // Loop until we're done.
567        while !self.state.should_quit {
568            self.queue
569                .blocking_dispatch(&mut self.state)
570                .map_err(Error::WaylandCommunication)?;
571
572            // Check if all sources have been destroyed.
573            let all_destroyed = self.sources.iter().all(|x| !x.is_alive());
574            if all_destroyed {
575                self.state.should_quit = true;
576            }
577        }
578
579        // Check if an error occurred during data transfer.
580        if let Some(err) = self.state.error.take() {
581            return Err(Error::Paste(err));
582        }
583
584        Ok(())
585    }
586}
587
588fn make_source(
589    source: Source,
590    mime_type: MimeType,
591    trim_newline: bool,
592) -> Result<DataSource, SourceCreationError> {
593    let mut output_place = if let Source::Bytes(data) = source {
594        data.into_vec()
595    } else {
596        let mut contents = Cursor::new(Vec::new());
597        io::copy(&mut io::stdin(), &mut contents).map_err(SourceCreationError::DataCopy)?;
598        contents.into_inner()
599    };
600
601    let mime_type = match mime_type {
602        MimeType::Autodetect => tree_magic_mini::from_u8(&output_place).to_string(),
603        MimeType::Text => TEXT_PLAIN_MIME.to_string(),
604        MimeType::Specific(mime_type) => mime_type,
605    };
606    log::trace!("Base MIME type: {}", mime_type);
607
608    // Trim the trailing newline if needed.
609    if trim_newline && is_text(&mime_type) && output_place.last().copied() == Some(b'\n') {
610        output_place.pop();
611    }
612
613    Ok(DataSource {
614        mime_type,
615        source: DataSourceStorage(Arc::from(output_place)),
616    })
617}
618
619fn get_devices(
620    primary: bool,
621    seat: Seat,
622    socket_name: Option<OsString>,
623) -> Result<(EventQueue<State>, State, Vec<data_control::Device>), Error> {
624    let (mut queue, mut common) = initialize(primary, socket_name)?;
625
626    // Check if there are no seats.
627    if common.seats.is_empty() {
628        return Err(Error::NoSeats);
629    }
630
631    // Go through the seats and get their data devices.
632    for (seat, data) in &mut common.seats {
633        let device = common
634            .clipboard_manager
635            .get_data_device(seat, &queue.handle(), seat.clone());
636        data.set_device(Some(device));
637    }
638
639    let mut state = State {
640        common,
641        got_primary_selection: false,
642        should_quit: false,
643        data_sources: HashMap::new(),
644        serve_requests: ServeRequests::default(),
645        error: None,
646    };
647
648    // Retrieve all seat names.
649    queue
650        .roundtrip(&mut state)
651        .map_err(Error::WaylandCommunication)?;
652
653    // Check if the compositor supports primary selection.
654    if primary && !state.got_primary_selection {
655        return Err(Error::PrimarySelectionUnsupported);
656    }
657
658    // Figure out which devices we're interested in.
659    let devices = state
660        .common
661        .seats
662        .values()
663        .filter_map(|data| {
664            let SeatData { name, device, .. } = data;
665
666            let device = device.clone();
667
668            match seat {
669                Seat::All => {
670                    // If no seat was specified, handle all of them.
671                    return device;
672                }
673                Seat::Specific(ref desired_name) => {
674                    if name.as_deref() == Some(desired_name) {
675                        return device;
676                    }
677                }
678            }
679
680            None
681        })
682        .collect::<Vec<_>>();
683
684    // If we didn't find the seat, print an error message and exit.
685    //
686    // This also triggers when we found the seat but it had no data device; is this what we want?
687    if devices.is_empty() {
688        return Err(Error::SeatNotFound);
689    }
690
691    Ok((queue, state, devices))
692}
693
694/// Clears the clipboard for the given seat.
695///
696/// If `seat` is `None`, clears clipboards of all existing seats.
697///
698/// # Examples
699///
700/// ```no_run
701/// # extern crate wl_clipboard_rs;
702/// # use wl_clipboard_rs::copy::Error;
703/// # fn foo() -> Result<(), Error> {
704/// use wl_clipboard_rs::copy::{clear, ClipboardType, Seat};
705///
706/// clear(ClipboardType::Regular, Seat::All)?;
707/// # Ok(())
708/// # }
709/// ```
710#[inline]
711pub fn clear(clipboard: ClipboardType, seat: Seat) -> Result<(), Error> {
712    clear_internal(clipboard, seat, None)
713}
714
715pub(crate) fn clear_internal(
716    clipboard: ClipboardType,
717    seat: Seat,
718    socket_name: Option<OsString>,
719) -> Result<(), Error> {
720    let primary = clipboard != ClipboardType::Regular;
721    let (mut queue, mut state, devices) = get_devices(primary, seat, socket_name)?;
722
723    for device in devices {
724        if clipboard == ClipboardType::Primary || clipboard == ClipboardType::Both {
725            device.set_primary_selection(None);
726        }
727        if clipboard == ClipboardType::Regular || clipboard == ClipboardType::Both {
728            device.set_selection(None);
729        }
730    }
731
732    // We're clearing the clipboard so just do one roundtrip and quit.
733    queue
734        .roundtrip(&mut state)
735        .map_err(Error::WaylandCommunication)?;
736
737    Ok(())
738}
739
740/// Prepares a data copy to the clipboard.
741///
742/// The data is copied from `source` and offered in the `mime_type` MIME type. See `Options` for
743/// customizing the behavior of this operation.
744///
745/// This function can be used instead of `copy()` when it's desirable to separately prepare the
746/// copy operation, handle any errors that this may produce, and then start the serving loop,
747/// potentially past a fork (which is how `wl-copy` uses it). It is meant to be used in the
748/// foreground mode and does not spawn any threads.
749///
750/// # Panics
751///
752/// Panics if `foreground` is `false`.
753///
754/// # Examples
755///
756/// ```no_run
757/// # extern crate wl_clipboard_rs;
758/// # use wl_clipboard_rs::copy::Error;
759/// # fn foo() -> Result<(), Error> {
760/// use wl_clipboard_rs::copy::{MimeSource, MimeType, Options, Source};
761///
762/// let mut opts = Options::new();
763/// opts.foreground(true);
764/// let prepared_copy =
765///     opts.prepare_copy(Source::Bytes([1, 2, 3][..].into()), MimeType::Autodetect)?;
766/// prepared_copy.serve()?;
767///
768/// # Ok(())
769/// # }
770/// ```
771#[inline]
772pub fn prepare_copy(
773    options: Options,
774    source: Source,
775    mime_type: MimeType,
776) -> Result<PreparedCopy, Error> {
777    assert!(options.foreground);
778
779    let sources = vec![MimeSource { source, mime_type }];
780
781    prepare_copy_internal(options, sources, None)
782}
783
784/// Prepares a data copy to the clipboard, offering multiple data sources.
785///
786/// The data from each source in `sources` is copied and offered in the corresponding MIME type.
787/// See `Options` for customizing the behavior of this operation.
788///
789/// If multiple sources specify the same MIME type, the first one is offered. If one of the MIME
790/// types is text, all automatically added plain text offers will fall back to the first source
791/// with a text MIME type.
792///
793/// This function can be used instead of `copy()` when it's desirable to separately prepare the
794/// copy operation, handle any errors that this may produce, and then start the serving loop,
795/// potentially past a fork (which is how `wl-copy` uses it). It is meant to be used in the
796/// foreground mode and does not spawn any threads.
797///
798/// # Panics
799///
800/// Panics if `foreground` is `false`.
801///
802/// # Examples
803///
804/// ```no_run
805/// # extern crate wl_clipboard_rs;
806/// # use wl_clipboard_rs::copy::Error;
807/// # fn foo() -> Result<(), Error> {
808/// use wl_clipboard_rs::copy::{MimeSource, MimeType, Options, Source};
809///
810/// let mut opts = Options::new();
811/// opts.foreground(true);
812/// let prepared_copy = opts.prepare_copy_multi(vec![
813///     MimeSource {
814///         source: Source::Bytes([1, 2, 3][..].into()),
815///         mime_type: MimeType::Autodetect,
816///     },
817///     MimeSource {
818///         source: Source::Bytes([7, 8, 9][..].into()),
819///         mime_type: MimeType::Text,
820///     },
821/// ])?;
822/// prepared_copy.serve()?;
823///
824/// # Ok(())
825/// # }
826/// ```
827#[inline]
828pub fn prepare_copy_multi(
829    options: Options,
830    sources: Vec<MimeSource>,
831) -> Result<PreparedCopy, Error> {
832    assert!(options.foreground);
833
834    prepare_copy_internal(options, sources, None)
835}
836
837fn prepare_copy_internal(
838    options: Options,
839    sources: Vec<MimeSource>,
840    socket_name: Option<OsString>,
841) -> Result<PreparedCopy, Error> {
842    let Options {
843        clipboard,
844        seat,
845        trim_newline,
846        serve_requests,
847        sensitive,
848        ..
849    } = options;
850
851    let primary = clipboard != ClipboardType::Regular;
852    let (queue, mut state, devices) = get_devices(primary, seat, socket_name)?;
853
854    state.serve_requests = serve_requests;
855
856    // Collect the source data to copy.
857    state.data_sources = {
858        let mut data_sources = HashMap::new();
859        let mut text_data = None;
860
861        for MimeSource { source, mime_type } in sources.into_iter() {
862            let DataSource { mime_type, source } =
863                make_source(source, mime_type, trim_newline).map_err(Error::TempCopy)?;
864
865            match data_sources.entry(mime_type) {
866                // This MIME type has already been specified, so ignore it.
867                Entry::Occupied(_) => drop(source),
868                Entry::Vacant(entry) => {
869                    if !options.omit_additional_text_mime_types
870                        && text_data.is_none()
871                        && is_text(entry.key())
872                    {
873                        text_data = Some(source.clone());
874                    }
875
876                    entry.insert(source);
877                }
878            }
879        }
880
881        // If the MIME type is text, offer it in some other common formats.
882        if let Some(text_data) = text_data {
883            let text_mimes = [
884                "text/plain;charset=utf-8",
885                TEXT_PLAIN_MIME,
886                "STRING",
887                "UTF8_STRING",
888                "TEXT",
889            ];
890
891            for mime_type in text_mimes {
892                // We don't want to overwrite an explicit mime type, because it might be bound to a
893                // different data_path
894                if !data_sources.contains_key(mime_type) {
895                    data_sources.insert(mime_type.to_string(), text_data.clone());
896                }
897            }
898        }
899
900        if sensitive {
901            data_sources
902                .entry(PASSWORD_MANAGER_HINT_MIME_TYPE.to_owned())
903                .or_insert_with(|| DataSourceStorage(Arc::from(&b"secret"[..])));
904        }
905
906        data_sources
907    };
908
909    // Create an iterator over (device, primary) for source creation later.
910    //
911    // This is needed because for ClipboardType::Both each device needs to appear twice because
912    // separate data sources need to be made for the regular and the primary clipboards (data
913    // sources cannot be reused).
914    let devices_iter = devices.iter().flat_map(|device| {
915        let first = match clipboard {
916            ClipboardType::Regular => iter::once((device, false)),
917            ClipboardType::Primary => iter::once((device, true)),
918            ClipboardType::Both => iter::once((device, false)),
919        };
920
921        let second = if clipboard == ClipboardType::Both {
922            iter::once(Some((device, true)))
923        } else {
924            iter::once(None)
925        };
926
927        first.chain(second.flatten())
928    });
929
930    // Create the data sources and set them as selections.
931    let sources = devices_iter
932        .map(|(device, primary)| {
933            let data_source = state
934                .common
935                .clipboard_manager
936                .create_data_source(&queue.handle());
937
938            for mime_type in state.data_sources.keys() {
939                if mime_type != PASSWORD_MANAGER_HINT_MIME_TYPE {
940                    data_source.offer(mime_type.clone());
941                }
942            }
943            // Advertise the hint after the actual contents. Some tools will choose the first
944            // offered MIME type as the "best" one, and we don't want that type to be the password
945            // manager hint.
946            if state
947                .data_sources
948                .contains_key(PASSWORD_MANAGER_HINT_MIME_TYPE)
949            {
950                data_source.offer(PASSWORD_MANAGER_HINT_MIME_TYPE.to_owned());
951            }
952
953            if primary {
954                device.set_primary_selection(Some(&data_source));
955            } else {
956                device.set_selection(Some(&data_source));
957            }
958
959            // If we need to serve 0 requests, kill the data source right away.
960            if let ServeRequests::Only(0) = state.serve_requests {
961                data_source.destroy();
962            }
963            data_source
964        })
965        .collect::<Vec<_>>();
966
967    Ok(PreparedCopy {
968        queue,
969        state,
970        sources,
971    })
972}
973
974/// Copies data to the clipboard.
975///
976/// The data is copied from `source` and offered in the `mime_type` MIME type. See `Options` for
977/// customizing the behavior of this operation.
978///
979/// # Examples
980///
981/// ```no_run
982/// # extern crate wl_clipboard_rs;
983/// # use wl_clipboard_rs::copy::Error;
984/// # fn foo() -> Result<(), Error> {
985/// use wl_clipboard_rs::copy::{copy, MimeType, Options, Source};
986///
987/// let opts = Options::new();
988/// copy(
989///     opts,
990///     Source::Bytes([1, 2, 3][..].into()),
991///     MimeType::Autodetect,
992/// )?;
993/// # Ok(())
994/// # }
995/// ```
996#[inline]
997pub fn copy(options: Options, source: Source, mime_type: MimeType) -> Result<(), Error> {
998    let sources = vec![MimeSource { source, mime_type }];
999    copy_internal(options, sources, None)
1000}
1001
1002/// Copies data to the clipboard, offering multiple data sources.
1003///
1004/// The data from each source in `sources` is copied and offered in the corresponding MIME type.
1005/// See `Options` for customizing the behavior of this operation.
1006///
1007/// If multiple sources specify the same MIME type, the first one is offered. If one of the MIME
1008/// types is text, all automatically added plain text offers will fall back to the first source
1009/// with a text MIME type.
1010///
1011/// # Examples
1012///
1013/// ```no_run
1014/// # extern crate wl_clipboard_rs;
1015/// # use wl_clipboard_rs::copy::Error;
1016/// # fn foo() -> Result<(), Error> {
1017/// use wl_clipboard_rs::copy::{MimeSource, MimeType, Options, Source};
1018///
1019/// let opts = Options::new();
1020/// opts.copy_multi(vec![
1021///     MimeSource {
1022///         source: Source::Bytes([1, 2, 3][..].into()),
1023///         mime_type: MimeType::Autodetect,
1024///     },
1025///     MimeSource {
1026///         source: Source::Bytes([7, 8, 9][..].into()),
1027///         mime_type: MimeType::Text,
1028///     },
1029/// ])?;
1030/// # Ok(())
1031/// # }
1032/// ```
1033#[inline]
1034pub fn copy_multi(options: Options, sources: Vec<MimeSource>) -> Result<(), Error> {
1035    copy_internal(options, sources, None)
1036}
1037
1038pub(crate) fn copy_internal(
1039    options: Options,
1040    sources: Vec<MimeSource>,
1041    socket_name: Option<OsString>,
1042) -> Result<(), Error> {
1043    if options.foreground {
1044        prepare_copy_internal(options, sources, socket_name)?.serve()
1045    } else {
1046        // The copy must be prepared on the thread because PreparedCopy isn't Send.
1047        // To receive errors from prepare_copy, use a channel.
1048        let (tx, rx) = sync_channel(1);
1049
1050        thread::spawn(
1051            move || match prepare_copy_internal(options, sources, socket_name) {
1052                Ok(prepared_copy) => {
1053                    // prepare_copy completed successfully, report that.
1054                    drop(tx.send(None));
1055
1056                    // There's nobody listening for errors at this point, just drop it.
1057                    drop(prepared_copy.serve());
1058                }
1059                Err(err) => drop(tx.send(Some(err))),
1060            },
1061        );
1062
1063        if let Some(err) = rx.recv().unwrap() {
1064            return Err(err);
1065        }
1066
1067        Ok(())
1068    }
1069}