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