wl_clipboard_rs/
copy.rs

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