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}