1use std::{
16 borrow::Cow,
17 cell::RefCell,
18 collections::{hash_map::Entry, HashMap},
19 path::PathBuf,
20 sync::{
21 atomic::{AtomicBool, Ordering},
22 Arc,
23 },
24 thread::JoinHandle,
25 thread_local,
26 time::{Duration, Instant},
27};
28
29use log::{error, trace, warn};
30use parking_lot::{Condvar, Mutex, MutexGuard, RwLock};
31use x11rb::{
32 connection::Connection,
33 protocol::{
34 xproto::{
35 Atom, AtomEnum, ConnectionExt as _, CreateWindowAux, EventMask, PropMode, Property,
36 PropertyNotifyEvent, SelectionNotifyEvent, SelectionRequestEvent, Time, WindowClass,
37 SELECTION_NOTIFY_EVENT,
38 },
39 Event,
40 },
41 rust_connection::RustConnection,
42 wrapper::ConnectionExt as _,
43 COPY_DEPTH_FROM_PARENT, COPY_FROM_PARENT, NONE,
44};
45
46#[cfg(feature = "image-data")]
47use super::encode_as_png;
48use super::{into_unknown, paths_from_uri_list, LinuxClipboardKind, WaitConfig};
49#[cfg(feature = "image-data")]
50use crate::ImageData;
51use crate::{common::ScopeGuard, Error};
52
53type Result<T, E = Error> = std::result::Result<T, E>;
54
55static CLIPBOARD: Mutex<Option<GlobalClipboard>> = parking_lot::const_mutex(None);
56
57x11rb::atom_manager! {
58 pub Atoms: AtomCookies {
59 CLIPBOARD,
60 PRIMARY,
61 SECONDARY,
62
63 CLIPBOARD_MANAGER,
64 SAVE_TARGETS,
65 TARGETS,
66 ATOM,
67 INCR,
68
69 UTF8_STRING,
70 UTF8_MIME_0: b"text/plain;charset=utf-8",
71 UTF8_MIME_1: b"text/plain;charset=UTF-8",
72 STRING,
75 TEXT,
78 TEXT_MIME_UNKNOWN: b"text/plain",
79
80 HTML: b"text/html",
81 URI_LIST: b"text/uri-list",
82
83 PNG_MIME: b"image/png",
84
85 ARBOARD_CLIPBOARD,
88 }
89}
90
91thread_local! {
92 static ATOM_NAME_CACHE: RefCell<HashMap<Atom, &'static str>> = Default::default();
93}
94
95const LONG_TIMEOUT_DUR: Duration = Duration::from_millis(4000);
98const SHORT_TIMEOUT_DUR: Duration = Duration::from_millis(10);
99
100#[derive(Debug, PartialEq, Eq)]
101enum ManagerHandoverState {
102 Idle,
103 InProgress,
104 Finished,
105}
106
107struct GlobalClipboard {
108 inner: Arc<Inner>,
109
110 server_handle: JoinHandle<()>,
112}
113
114struct XContext {
115 conn: RustConnection,
116 win_id: u32,
117}
118
119struct Inner {
120 server: XContext,
123 atoms: Atoms,
124
125 clipboard: Selection,
126 primary: Selection,
127 secondary: Selection,
128
129 handover_state: Mutex<ManagerHandoverState>,
130 handover_cv: Condvar,
131
132 serve_stopped: AtomicBool,
133}
134
135impl XContext {
136 fn new() -> Result<Self> {
137 let (conn, screen_num): (RustConnection, _) =
139 RustConnection::connect(None).map_err(|_| {
140 Error::unknown("X11 server connection timed out because it was unreachable")
141 })?;
142 let screen = conn.setup().roots.get(screen_num).ok_or(Error::unknown("no screen found"))?;
143 let win_id = conn.generate_id().map_err(into_unknown)?;
144
145 let event_mask =
146 EventMask::PROPERTY_CHANGE |
149 EventMask::STRUCTURE_NOTIFY;
151 conn.create_window(
153 COPY_DEPTH_FROM_PARENT,
155 win_id,
156 screen.root,
157 0,
158 0,
159 1,
160 1,
161 0,
162 WindowClass::COPY_FROM_PARENT,
163 COPY_FROM_PARENT,
164 &CreateWindowAux::new().event_mask(event_mask),
166 )
167 .map_err(into_unknown)?;
168 conn.flush().map_err(into_unknown)?;
169
170 Ok(Self { conn, win_id })
171 }
172}
173
174#[derive(Default)]
175struct Selection {
176 data: RwLock<Option<Vec<ClipboardData>>>,
177 mutex: Mutex<()>,
179 data_changed: Condvar,
183}
184
185#[derive(Debug, Clone)]
186struct ClipboardData {
187 bytes: Vec<u8>,
188
189 format: Atom,
191}
192
193enum ReadSelNotifyResult {
194 GotData(Vec<u8>),
195 IncrStarted,
196 EventNotRecognized,
197}
198
199impl Inner {
200 fn new() -> Result<Self> {
201 let server = XContext::new()?;
202 let atoms =
203 Atoms::new(&server.conn).map_err(into_unknown)?.reply().map_err(into_unknown)?;
204
205 Ok(Self {
206 server,
207 atoms,
208 clipboard: Selection::default(),
209 primary: Selection::default(),
210 secondary: Selection::default(),
211 handover_state: Mutex::new(ManagerHandoverState::Idle),
212 handover_cv: Condvar::new(),
213 serve_stopped: AtomicBool::new(false),
214 })
215 }
216
217 fn write(
218 &self,
219 data: Vec<ClipboardData>,
220 selection: LinuxClipboardKind,
221 wait: WaitConfig,
222 ) -> Result<()> {
223 if self.serve_stopped.load(Ordering::Relaxed) {
224 return Err(Error::unknown("The clipboard handler thread seems to have stopped. Logging messages may reveal the cause. (See the `log` crate.)"));
225 }
226
227 let server_win = self.server.win_id;
228
229 self.server
232 .conn
233 .set_selection_owner(server_win, self.atom_of(selection), Time::CURRENT_TIME)
234 .map_err(|_| Error::ClipboardOccupied)?;
235
236 self.server.conn.flush().map_err(into_unknown)?;
237
238 let selection = self.selection_of(selection);
240 let mut data_guard = selection.data.write();
241 *data_guard = Some(data);
242
243 let mut guard = selection.mutex.lock();
247
248 selection.data_changed.notify_all();
251
252 match wait {
253 WaitConfig::None => {}
254 WaitConfig::Forever => {
255 drop(data_guard);
256 selection.data_changed.wait(&mut guard);
257 }
258
259 WaitConfig::Until(deadline) => {
260 drop(data_guard);
261 selection.data_changed.wait_until(&mut guard, deadline);
262 }
263 }
264
265 Ok(())
266 }
267
268 fn read(&self, formats: &[Atom], selection: LinuxClipboardKind) -> Result<ClipboardData> {
272 if self.is_owner(selection)? {
274 let data = self.selection_of(selection).data.read();
275 if let Some(data_list) = &*data {
276 for data in data_list {
277 for format in formats {
278 if *format == data.format {
279 return Ok(data.clone());
280 }
281 }
282 }
283 }
284 return Err(Error::ContentNotAvailable);
285 }
286 let reader = XContext::new()?;
290
291 trace!("Trying to get the clipboard data.");
292 for format in formats {
293 match self.read_single(&reader, selection, *format) {
294 Ok(bytes) => {
295 return Ok(ClipboardData { bytes, format: *format });
296 }
297 Err(Error::ContentNotAvailable) => {
298 continue;
299 }
300 Err(e) => return Err(e),
301 }
302 }
303 Err(Error::ContentNotAvailable)
304 }
305
306 fn read_single(
307 &self,
308 reader: &XContext,
309 selection: LinuxClipboardKind,
310 target_format: Atom,
311 ) -> Result<Vec<u8>> {
312 reader
315 .conn
316 .delete_property(reader.win_id, self.atoms.ARBOARD_CLIPBOARD)
317 .map_err(into_unknown)?;
318
319 reader
321 .conn
322 .convert_selection(
323 reader.win_id,
324 self.atom_of(selection),
325 target_format,
326 self.atoms.ARBOARD_CLIPBOARD,
327 Time::CURRENT_TIME,
328 )
329 .map_err(into_unknown)?;
330 reader.conn.sync().map_err(into_unknown)?;
331
332 trace!("Finished `convert_selection`");
333
334 let mut incr_data: Vec<u8> = Vec::new();
335 let mut using_incr = false;
336
337 let mut timeout_end = Instant::now() + LONG_TIMEOUT_DUR;
338
339 while Instant::now() < timeout_end {
340 let event = reader.conn.poll_for_event().map_err(into_unknown)?;
341 let event = match event {
342 Some(e) => e,
343 None => {
344 std::thread::sleep(Duration::from_millis(1));
345 continue;
346 }
347 };
348 match event {
349 Event::SelectionNotify(event) => {
351 trace!("Read SelectionNotify");
352 let result = self.handle_read_selection_notify(
353 reader,
354 target_format,
355 &mut using_incr,
356 &mut incr_data,
357 event,
358 )?;
359 match result {
360 ReadSelNotifyResult::GotData(data) => return Ok(data),
361 ReadSelNotifyResult::IncrStarted => {
362 timeout_end += SHORT_TIMEOUT_DUR;
366 }
367 ReadSelNotifyResult::EventNotRecognized => (),
368 }
369 }
370 Event::PropertyNotify(event) => {
374 let result = self.handle_read_property_notify(
375 reader,
376 target_format,
377 using_incr,
378 &mut incr_data,
379 &mut timeout_end,
380 event,
381 )?;
382 if result {
383 return Ok(incr_data);
384 }
385 }
386 _ => log::trace!("An unexpected event arrived while reading the clipboard."),
387 }
388 }
389 log::info!("Time-out hit while reading the clipboard.");
390 Err(Error::ContentNotAvailable)
391 }
392
393 fn atom_of(&self, selection: LinuxClipboardKind) -> Atom {
394 match selection {
395 LinuxClipboardKind::Clipboard => self.atoms.CLIPBOARD,
396 LinuxClipboardKind::Primary => self.atoms.PRIMARY,
397 LinuxClipboardKind::Secondary => self.atoms.SECONDARY,
398 }
399 }
400
401 fn selection_of(&self, selection: LinuxClipboardKind) -> &Selection {
402 match selection {
403 LinuxClipboardKind::Clipboard => &self.clipboard,
404 LinuxClipboardKind::Primary => &self.primary,
405 LinuxClipboardKind::Secondary => &self.secondary,
406 }
407 }
408
409 fn kind_of(&self, atom: Atom) -> Option<LinuxClipboardKind> {
410 match atom {
411 a if a == self.atoms.CLIPBOARD => Some(LinuxClipboardKind::Clipboard),
412 a if a == self.atoms.PRIMARY => Some(LinuxClipboardKind::Primary),
413 a if a == self.atoms.SECONDARY => Some(LinuxClipboardKind::Secondary),
414 _ => None,
415 }
416 }
417
418 fn is_owner(&self, selection: LinuxClipboardKind) -> Result<bool> {
419 let current = self
420 .server
421 .conn
422 .get_selection_owner(self.atom_of(selection))
423 .map_err(into_unknown)?
424 .reply()
425 .map_err(into_unknown)?
426 .owner;
427
428 Ok(current == self.server.win_id)
429 }
430
431 fn atom_name(&self, atom: x11rb::protocol::xproto::Atom) -> Result<String> {
432 String::from_utf8(
433 self.server
434 .conn
435 .get_atom_name(atom)
436 .map_err(into_unknown)?
437 .reply()
438 .map_err(into_unknown)?
439 .name,
440 )
441 .map_err(into_unknown)
442 }
443 fn atom_name_dbg(&self, atom: x11rb::protocol::xproto::Atom) -> &'static str {
444 ATOM_NAME_CACHE.with(|cache| {
445 let mut cache = cache.borrow_mut();
446 match cache.entry(atom) {
447 Entry::Occupied(entry) => *entry.get(),
448 Entry::Vacant(entry) => {
449 let s = self
450 .atom_name(atom)
451 .map(|s| Box::leak(s.into_boxed_str()) as &str)
452 .unwrap_or("FAILED-TO-GET-THE-ATOM-NAME");
453 entry.insert(s);
454 s
455 }
456 }
457 })
458 }
459
460 fn handle_read_selection_notify(
461 &self,
462 reader: &XContext,
463 target_format: u32,
464 using_incr: &mut bool,
465 incr_data: &mut Vec<u8>,
466 event: SelectionNotifyEvent,
467 ) -> Result<ReadSelNotifyResult> {
468 if event.property == NONE || event.target != target_format {
474 return Err(Error::ContentNotAvailable);
475 }
476 if self.kind_of(event.selection).is_none() {
477 log::info!("Received a SelectionNotify for a selection other than CLIPBOARD, PRIMARY or SECONDARY. This is unexpected.");
478 return Ok(ReadSelNotifyResult::EventNotRecognized);
479 }
480 if *using_incr {
481 log::warn!("Received a SelectionNotify while already expecting INCR segments.");
482 return Ok(ReadSelNotifyResult::EventNotRecognized);
483 }
484 let mut reply = reader
486 .conn
487 .get_property(true, event.requestor, event.property, event.target, 0, u32::MAX / 4)
488 .map_err(into_unknown)?
489 .reply()
490 .map_err(into_unknown)?;
491
492 if reply.type_ == target_format {
496 Ok(ReadSelNotifyResult::GotData(reply.value))
497 } else if reply.type_ == self.atoms.INCR {
498 reply = reader
503 .conn
504 .get_property(
505 true,
506 event.requestor,
507 event.property,
508 self.atoms.INCR,
509 0,
510 u32::MAX / 4,
511 )
512 .map_err(into_unknown)?
513 .reply()
514 .map_err(into_unknown)?;
515 log::trace!("Receiving INCR segments");
516 *using_incr = true;
517 if reply.value_len == 4 {
518 let min_data_len = reply.value32().and_then(|mut vals| vals.next()).unwrap_or(0);
519 incr_data.reserve(min_data_len as usize);
520 }
521 Ok(ReadSelNotifyResult::IncrStarted)
522 } else {
523 Err(Error::unknown("incorrect type received from clipboard"))
525 }
526 }
527
528 fn handle_read_property_notify(
530 &self,
531 reader: &XContext,
532 target_format: u32,
533 using_incr: bool,
534 incr_data: &mut Vec<u8>,
535 timeout_end: &mut Instant,
536 event: PropertyNotifyEvent,
537 ) -> Result<bool> {
538 if event.atom != self.atoms.ARBOARD_CLIPBOARD || event.state != Property::NEW_VALUE {
539 return Ok(false);
540 }
541 if !using_incr {
542 return Ok(false);
545 }
546 let reply = reader
547 .conn
548 .get_property(true, event.window, event.atom, target_format, 0, u32::MAX / 4)
549 .map_err(into_unknown)?
550 .reply()
551 .map_err(into_unknown)?;
552
553 if reply.value_len == 0 {
555 return Ok(true);
557 }
558 incr_data.extend(reply.value);
559
560 *timeout_end = Instant::now() + SHORT_TIMEOUT_DUR;
562
563 Ok(false)
565 }
566
567 fn handle_selection_request(&self, event: SelectionRequestEvent) -> Result<()> {
568 let selection = match self.kind_of(event.selection) {
569 Some(kind) => kind,
570 None => {
571 warn!("Received a selection request to a selection other than the CLIPBOARD, PRIMARY or SECONDARY. This is unexpected.");
572 return Ok(());
573 }
574 };
575
576 let success;
577 if event.target == self.atoms.TARGETS {
579 trace!("Handling TARGETS, dst property is {}", self.atom_name_dbg(event.property));
580 let mut targets = Vec::with_capacity(10);
581 targets.push(self.atoms.TARGETS);
582 targets.push(self.atoms.SAVE_TARGETS);
583 let data = self.selection_of(selection).data.read();
584 if let Some(data_list) = &*data {
585 for data in data_list {
586 targets.push(data.format);
587 if data.format == self.atoms.UTF8_STRING {
588 targets.push(self.atoms.UTF8_MIME_0);
591 targets.push(self.atoms.UTF8_MIME_1);
592 }
593 }
594 }
595 self.server
596 .conn
597 .change_property32(
598 PropMode::REPLACE,
599 event.requestor,
600 event.property,
601 self.atoms.ATOM,
603 &targets,
604 )
605 .map_err(into_unknown)?;
606 self.server.conn.flush().map_err(into_unknown)?;
607 success = true;
608 } else {
609 trace!("Handling request for (probably) the clipboard contents.");
610 let data = self.selection_of(selection).data.read();
611 if let Some(data_list) = &*data {
612 success = match data_list.iter().find(|d| d.format == event.target) {
613 Some(data) => {
614 self.server
615 .conn
616 .change_property8(
617 PropMode::REPLACE,
618 event.requestor,
619 event.property,
620 event.target,
621 &data.bytes,
622 )
623 .map_err(into_unknown)?;
624 self.server.conn.flush().map_err(into_unknown)?;
625 true
626 }
627 None => false,
628 };
629 } else {
630 success = false;
634 }
635 }
636 let property = if success { event.property } else { AtomEnum::NONE.into() };
638 self.server
640 .conn
641 .send_event(
642 false,
643 event.requestor,
644 EventMask::NO_EVENT,
645 SelectionNotifyEvent {
646 response_type: SELECTION_NOTIFY_EVENT,
647 sequence: event.sequence,
648 time: event.time,
649 requestor: event.requestor,
650 selection: event.selection,
651 target: event.target,
652 property,
653 },
654 )
655 .map_err(into_unknown)?;
656
657 self.server.conn.flush().map_err(into_unknown)
658 }
659
660 fn ask_clipboard_manager_to_request_our_data(&self) -> Result<()> {
661 if self.server.win_id == 0 {
662 error!("The server's window id was 0. This is unexpected");
664 return Ok(());
665 }
666
667 if !self.is_owner(LinuxClipboardKind::Clipboard)? {
668 return Ok(());
670 }
671 if self.selection_of(LinuxClipboardKind::Clipboard).data.read().is_none() {
672 return Ok(());
674 }
675
676 let mut handover_state = self.handover_state.lock();
680
681 trace!("Sending the data to the clipboard manager");
682 self.server
683 .conn
684 .convert_selection(
685 self.server.win_id,
686 self.atoms.CLIPBOARD_MANAGER,
687 self.atoms.SAVE_TARGETS,
688 self.atoms.ARBOARD_CLIPBOARD,
689 Time::CURRENT_TIME,
690 )
691 .map_err(into_unknown)?;
692 self.server.conn.flush().map_err(into_unknown)?;
693
694 *handover_state = ManagerHandoverState::InProgress;
695 let max_handover_duration = Duration::from_millis(100);
696
697 let result = self.handover_cv.wait_for(&mut handover_state, max_handover_duration);
700
701 if *handover_state == ManagerHandoverState::Finished {
702 return Ok(());
703 }
704 if result.timed_out() {
705 warn!("Could not hand the clipboard contents over to the clipboard manager. The request timed out.");
706 return Ok(());
707 }
708
709 Err(Error::unknown("The handover was not finished and the condvar didn't time out, yet the condvar wait ended. This should be unreachable."))
710 }
711}
712
713fn serve_requests(context: Arc<Inner>) -> Result<(), Box<dyn std::error::Error>> {
714 fn handover_finished(clip: &Arc<Inner>, mut handover_state: MutexGuard<ManagerHandoverState>) {
715 log::trace!("Finishing clipboard manager handover.");
716 *handover_state = ManagerHandoverState::Finished;
717
718 drop(handover_state);
720
721 clip.handover_cv.notify_all();
722 }
723
724 trace!("Started serve requests thread.");
725
726 let _guard = ScopeGuard::new(|| {
727 context.serve_stopped.store(true, Ordering::Relaxed);
728 });
729
730 let mut written = false;
731 let mut notified = false;
732
733 loop {
734 match context.server.conn.wait_for_event().map_err(into_unknown)? {
735 Event::DestroyNotify(_) => {
736 trace!("Clipboard server window is being destroyed x_x");
738 return Ok(());
739 }
740 Event::SelectionClear(event) => {
741 trace!("Somebody else owns the clipboard now");
745
746 if let Some(selection) = context.kind_of(event.selection) {
747 let selection = context.selection_of(selection);
748 let mut data_guard = selection.data.write();
749 *data_guard = None;
750
751 let _guard = selection.mutex.lock();
757 selection.data_changed.notify_all();
758 }
759 }
760 Event::SelectionRequest(event) => {
761 trace!(
762 "SelectionRequest - selection is: {}, target is {}",
763 context.atom_name_dbg(event.selection),
764 context.atom_name_dbg(event.target),
765 );
766 context.handle_selection_request(event).map_err(into_unknown)?;
768
769 let handover_state = context.handover_state.lock();
772 if *handover_state == ManagerHandoverState::InProgress {
773 if event.target != context.atoms.TARGETS {
776 trace!("The contents were written to the clipboard manager.");
777 written = true;
778 if notified {
780 handover_finished(&context, handover_state);
781 }
782 }
783 }
784 }
785 Event::SelectionNotify(event) => {
786 if event.selection != context.atoms.CLIPBOARD_MANAGER {
791 error!("Received a `SelectionNotify` from a selection other than the CLIPBOARD_MANAGER. This is unexpected in this thread.");
792 continue;
793 }
794 let handover_state = context.handover_state.lock();
795 if *handover_state == ManagerHandoverState::InProgress {
796 trace!("The clipboard manager indicated that it's done requesting the contents from us.");
800 notified = true;
801
802 if written {
809 handover_finished(&context, handover_state);
810 }
811 }
812 }
813 _event => {
814 }
817 }
818 }
819}
820
821pub(crate) struct Clipboard {
822 inner: Arc<Inner>,
823}
824
825impl Clipboard {
826 pub(crate) fn new() -> Result<Self> {
827 let mut global_cb = CLIPBOARD.lock();
828 if let Some(global_cb) = &*global_cb {
829 return Ok(Self { inner: Arc::clone(&global_cb.inner) });
830 }
831 let ctx = Arc::new(Inner::new()?);
833 let join_handle;
834 {
835 let ctx = Arc::clone(&ctx);
836 join_handle = std::thread::spawn(move || {
837 if let Err(error) = serve_requests(ctx) {
838 error!("Worker thread errored with: {}", error);
839 }
840 });
841 }
842 *global_cb = Some(GlobalClipboard { inner: Arc::clone(&ctx), server_handle: join_handle });
843 Ok(Self { inner: ctx })
844 }
845
846 pub(crate) fn get_text(&self, selection: LinuxClipboardKind) -> Result<String> {
847 let formats = [
848 self.inner.atoms.UTF8_STRING,
849 self.inner.atoms.UTF8_MIME_0,
850 self.inner.atoms.UTF8_MIME_1,
851 self.inner.atoms.STRING,
852 self.inner.atoms.TEXT,
853 self.inner.atoms.TEXT_MIME_UNKNOWN,
854 ];
855 let result = self.inner.read(&formats, selection)?;
856 if result.format == self.inner.atoms.STRING {
857 Ok(result.bytes.into_iter().map(|c| c as char).collect())
860 } else {
861 String::from_utf8(result.bytes).map_err(|_| Error::ConversionFailure)
862 }
863 }
864
865 pub(crate) fn set_text(
866 &self,
867 message: Cow<'_, str>,
868 selection: LinuxClipboardKind,
869 wait: WaitConfig,
870 ) -> Result<()> {
871 let data = vec![ClipboardData {
872 bytes: message.into_owned().into_bytes(),
873 format: self.inner.atoms.UTF8_STRING,
874 }];
875 self.inner.write(data, selection, wait)
876 }
877
878 pub(crate) fn get_html(&self, selection: LinuxClipboardKind) -> Result<String> {
879 let formats = [self.inner.atoms.HTML];
880 let result = self.inner.read(&formats, selection)?;
881 String::from_utf8(result.bytes).map_err(|_| Error::ConversionFailure)
882 }
883
884 pub(crate) fn set_html(
885 &self,
886 html: Cow<'_, str>,
887 alt: Option<Cow<'_, str>>,
888 selection: LinuxClipboardKind,
889 wait: WaitConfig,
890 ) -> Result<()> {
891 let mut data = vec![];
892 if let Some(alt_text) = alt {
893 data.push(ClipboardData {
894 bytes: alt_text.into_owned().into_bytes(),
895 format: self.inner.atoms.UTF8_STRING,
896 });
897 }
898 data.push(ClipboardData {
899 bytes: html.into_owned().into_bytes(),
900 format: self.inner.atoms.HTML,
901 });
902 self.inner.write(data, selection, wait)
903 }
904
905 #[cfg(feature = "image-data")]
906 pub(crate) fn get_image(&self, selection: LinuxClipboardKind) -> Result<ImageData<'static>> {
907 let formats = [self.inner.atoms.PNG_MIME];
908 let bytes = self.inner.read(&formats, selection)?.bytes;
909
910 let cursor = std::io::Cursor::new(&bytes);
911 let mut reader = image::io::Reader::new(cursor);
912 reader.set_format(image::ImageFormat::Png);
913 let image = match reader.decode() {
914 Ok(img) => img.into_rgba8(),
915 Err(_e) => return Err(Error::ConversionFailure),
916 };
917 let (w, h) = image.dimensions();
918 let image_data =
919 ImageData { width: w as usize, height: h as usize, bytes: image.into_raw().into() };
920 Ok(image_data)
921 }
922
923 #[cfg(feature = "image-data")]
924 pub(crate) fn set_image(
925 &self,
926 image: ImageData,
927 selection: LinuxClipboardKind,
928 wait: WaitConfig,
929 ) -> Result<()> {
930 let encoded = encode_as_png(&image)?;
931 let data = vec![ClipboardData { bytes: encoded, format: self.inner.atoms.PNG_MIME }];
932 self.inner.write(data, selection, wait)
933 }
934
935 pub(crate) fn get_file_list(&self, selection: LinuxClipboardKind) -> Result<Vec<PathBuf>> {
936 let result = self.inner.read(&[self.inner.atoms.URI_LIST], selection)?;
937
938 String::from_utf8(result.bytes)
939 .map_err(|_| Error::ConversionFailure)
940 .map(paths_from_uri_list)
941 }
942}
943
944impl Drop for Clipboard {
945 fn drop(&mut self) {
946 const MIN_OWNERS: usize = 3;
949
950 let mut global_cb = CLIPBOARD.lock();
953 if Arc::strong_count(&self.inner) == MIN_OWNERS {
954 if let Err(e) = self.inner.ask_clipboard_manager_to_request_our_data() {
959 error!("Could not hand the clipboard data over to the clipboard manager: {}", e);
960 }
961 let global_cb = global_cb.take();
962 if let Err(e) = self.inner.server.conn.destroy_window(self.inner.server.win_id) {
963 error!("Failed to destroy the clipboard window. Error: {}", e);
964 return;
965 }
966 if let Err(e) = self.inner.server.conn.flush() {
967 error!("Failed to flush the clipboard window. Error: {}", e);
968 return;
969 }
970 if let Some(global_cb) = global_cb {
971 if let Err(e) = global_cb.server_handle.join() {
972 let message;
974 if let Some(msg) = e.downcast_ref::<&'static str>() {
975 message = Some((*msg).to_string());
976 } else if let Some(msg) = e.downcast_ref::<String>() {
977 message = Some(msg.clone());
978 } else {
979 message = None;
980 }
981 if let Some(message) = message {
982 error!(
983 "The clipboard server thread panicked. Panic message: '{}'",
984 message,
985 );
986 } else {
987 error!("The clipboard server thread panicked.");
988 }
989 }
990 }
991 }
992 }
993}