1 /*
2 SPDX-License-Identifier: Apache-2.0 OR MIT
3 
4 Copyright 2020 The arboard contributors
5 
6 The project to which this file belongs is licensed under either of
7 the Apache 2.0 or the MIT license at the licensee's choice. The terms
8 and conditions of the chosen license apply to this file.
9 */
10 
11 //!
12 //!
13 //! This implementation is a port of https://github.com/dacap/clip to Rust
14 //! The structure of the original is more or less maintained.
15 //!
16 //! Disclaimer: The original C++ code is well organized and feels clean but it relies on C++
17 //! allowing a liberal data sharing between threads and it is painfully obvious from certain parts
18 //! of this port that this code was not designed for Rust. It should probably be reworked because
19 //! the absolute plague that the Arc<Mutex<>> objects are in this code is horrible just to look at
20 //! and will forever haunt me in my nightmares.
21 //!
22 //! Most changes are to conform with Rusts rules for example there are multiple overloads of
23 //! the `get_atom` functtion in the original but there's no function overloading in Rust so
24 //! those are split apart into functions with different names. (`get_atom_by_id` and the other one
25 //! at the time of writing I haven't needed to use)
26 //!
27 //! More noteably the `Manager` class had to be split into mutliple `structs` and some member
28 //! functions were made into global functions to conform Rust's aliasing rules.
29 //! Furthermore the signature of many functions was changed to follow a simple locking philosophy;
30 //! namely that the mutex gets locked at the topmost level possible and then most functions don't
31 //! need to attempt to lock, instead they just use the direct object references passed on as arguments.
32 //!
33 //!
34 
35 use std::collections::BTreeMap;
36 use std::sync::{Arc, Condvar, Mutex, MutexGuard};
37 use std::time::Duration;
38 
39 use lazy_static::lazy_static;
40 use x11rb::protocol::xproto;
41 use x11rb::{
42 	connection::Connection,
43 	protocol::{
44 		xproto::{
45 			Atom, AtomEnum, ConnectionExt as _, CreateWindowAux, EventMask, GetPropertyReply,
46 			PropMode, Property, PropertyNotifyEvent, SelectionClearEvent, SelectionNotifyEvent,
47 			SelectionRequestEvent, Time, Window, WindowClass,
48 		},
49 		Event,
50 	},
51 	rust_connection::RustConnection,
52 	wrapper::ConnectionExt as _,
53 };
54 
55 use crate::common_linux::encode_as_png;
56 
57 use super::common::{Error, ImageData};
58 
59 x11rb::atom_manager! {
60 	pub CommonAtoms: CommonAtomCookies {
61 		ATOM,
62 		INCR,
63 		TARGETS,
64 		CLIPBOARD,
65 		MIME_IMAGE_PNG: b"image/png",
66 		ATOM_PAIR,
67 		SAVE_TARGETS,
68 		MULTIPLE,
69 		CLIPBOARD_MANAGER,
70 	}
71 }
72 
73 x11rb::atom_manager! {
74 	pub TextAtoms: TextAtomCookies {
75 		UTF8_STRING,
76 		TEXT_PLAIN_1: b"text/plain;charset=utf-8",
77 		TEXT_PLAIN_2: b"text/plain;charset=UTF-8",
78 		// ANSI C strings?
79 		STRING,
80 		TEXT,
81 		TEXT_PLAIN_0: b"text/plain",
82 	}
83 }
84 
85 type BufferPtr = Option<Arc<Mutex<Vec<u8>>>>;
86 type Atoms = Vec<Atom>;
87 type NotifyCallback = Option<Arc<dyn (Fn(&BufferPtr) -> bool) + Send + Sync + 'static>>;
88 
89 lazy_static! {
90 	static ref LOCKED_OBJECTS: Arc<Mutex<Option<LockedObjects>>> = Arc::new(Mutex::new(None));
91 
92 	// Used to wait/notify the arrival of the SelectionNotify event when
93 	// we requested the clipboard content from other selection owner.
94 	static ref CONDVAR: Condvar = Condvar::new();
95 }
96 
97 struct LockedObjects {
98 	shared: SharedState,
99 	manager: Manager,
100 }
101 
102 impl LockedObjects {
new() -> Result<LockedObjects, Error>103 	fn new() -> Result<LockedObjects, Error> {
104 		let (connection, screen) = RustConnection::connect(None).unwrap();
105 		match Manager::new(&connection, screen) {
106 			Ok(manager) => {
107 				//unsafe { libc::atexit(Manager::destruct); }
108 				Ok(LockedObjects {
109 					shared: SharedState {
110 						conn: Some(Arc::new(connection)),
111 						common_atoms: Default::default(),
112 						text_atoms: Default::default(),
113 					},
114 					manager,
115 				})
116 			}
117 			Err(e) => Err(e),
118 		}
119 	}
120 }
121 
122 /// The name indicates that objects in this struct are shared between
123 /// the event processing thread and the user tread. However it's important
124 /// that the `Manager` itself is also shared. So the real reason for splitting these
125 /// apart from the `Manager` is to conform to Rust's aliasing rules but that is hard to
126 /// convey in a short name.
127 struct SharedState {
128 	conn: Option<Arc<RustConnection>>,
129 
130 	// Cache of common used atoms by us
131 	common_atoms: Option<CommonAtoms>,
132 
133 	// Cache of atoms related to text or image content
134 	text_atoms: Option<TextAtoms>,
135 	//image_atoms: Atoms,
136 }
137 
138 impl SharedState {
common_atoms(&mut self) -> CommonAtoms139 	fn common_atoms(&mut self) -> CommonAtoms {
140 		self.common_atoms.unwrap_or_else(|| {
141 			CommonAtoms::new(self.conn.as_ref().unwrap().as_ref()).unwrap().reply().unwrap()
142 		})
143 	}
144 
text_atoms(&mut self) -> Atoms145 	fn text_atoms(&mut self) -> Atoms {
146 		let atoms = self.text_atoms.unwrap_or_else(|| {
147 			TextAtoms::new(self.conn.as_ref().unwrap().as_ref()).unwrap().reply().unwrap()
148 		});
149 
150 		vec![
151 			atoms.UTF8_STRING,
152 			atoms.TEXT_PLAIN_1,
153 			atoms.TEXT_PLAIN_2,
154 			atoms.STRING,
155 			atoms.TEXT,
156 			atoms.TEXT_PLAIN_0,
157 		]
158 	}
159 }
160 
161 struct Manager {
162 	// Temporal background window used to own the clipboard and process
163 	// all events related about the clipboard in a background thread
164 	window: Window,
165 
166 	// Thread used to run a background message loop to wait X11 events
167 	// about clipboard. The X11 selection owner will be a hidden window
168 	// created by us just for the clipboard purpose/communication.
169 	thread_handle: Option<std::thread::JoinHandle<()>>,
170 
171 	// WARNING: The callback must not attempt to lock the manager or the shared state.
172 	// (Otherwise the code needs to be restructured slightly)
173 	//
174 	// Internal callback used when a SelectionNotify is received (or the
175 	// whole data content is received by the INCR method). So this
176 	// callback can use the notification by different purposes (e.g. get
177 	// the data length only, or get/process the data content, etc.).
178 	callback: NotifyCallback,
179 
180 	// Result returned by the m_callback. Used as return value in the
181 	// get_data_from_selection_owner() function. For example, if the
182 	// callback must read a "image/png" file from the clipboard data and
183 	// fails, the callback can return false and finally the get_image()
184 	// will return false (i.e. there is data, but it's not a valid image
185 	// format).
186 	callback_result: bool,
187 
188 	// Actual clipboard data generated by us (when we "copy" content in
189 	// the clipboard, it means that we own the X11 "CLIPBOARD"
190 	// selection, and in case of SelectionRequest events, we've to
191 	// return the data stored in this "m_data" field)
192 	data: BTreeMap<Atom, BufferPtr>,
193 
194 	// Copied image in the clipboard. As we have to transfer the image
195 	// in some specific format (e.g. image/png) we want to keep a copy
196 	// of the image and make the conversion when the clipboard data is
197 	// requested by other process.
198 	image: super::common::ImageData<'static>,
199 
200 	// True if we have received an INCR notification so we're going to
201 	// process several PropertyNotify to concatenate all data chunks.
202 	incr_process: bool,
203 
204 	/// Variable used to wait more time if we've received an INCR
205 	/// notification, which means that we're going to receive large
206 	/// amounts of data from the selection owner.
207 	///mutable bool m_incr_received;
208 	incr_received: bool,
209 
210 	// Target/selection format used in the SelectionNotify. Used in the
211 	// INCR method to get data from the same property in the same format
212 	// (target) on each PropertyNotify.
213 	target_atom: Atom,
214 
215 	// Each time we receive data from the selection owner, we put that
216 	// data in this buffer. If we get the data with the INCR method,
217 	// we'll concatenate chunks of data in this buffer to complete the
218 	// whole clipboard content.
219 	reply_data: BufferPtr,
220 
221 	// Used to concatenate chunks of data in "m_reply_data" from several
222 	// PropertyNotify when we are getting the selection owner data with
223 	// the INCR method.
224 	reply_offset: usize,
225 	// List of user-defined formats/atoms.
226 	//custom_formats: Vec<xcb::xproto::Atom>,
227 }
228 
229 impl Manager {
new(connection: &RustConnection, screen: usize) -> Result<Self, Error>230 	fn new(connection: &RustConnection, screen: usize) -> Result<Self, Error> {
231 		let setup = connection.setup();
232 		let screen = setup.roots.get(screen).ok_or(Error::Unknown {
233 			description: String::from("Could not get screen from setup"),
234 		})?;
235 		let event_mask =
236             // Just in case that some program reports SelectionNotify events
237             // with XCB_EVENT_MASK_PROPERTY_CHANGE mask.
238             EventMask::PROPERTY_CHANGE |
239             // To receive DestroyNotify event and stop the message loop.
240             EventMask::STRUCTURE_NOTIFY;
241 		let window = connection
242 			.generate_id()
243 			.map_err(|e| Error::Unknown { description: format!("{}", e) })?;
244 		connection
245 			.create_window(
246 				0,
247 				window,
248 				screen.root,
249 				0,
250 				0,
251 				1,
252 				1,
253 				0,
254 				WindowClass::INPUT_OUTPUT,
255 				screen.root_visual,
256 				&CreateWindowAux::new().event_mask(event_mask),
257 			)
258 			.map_err(|e| Error::Unknown { description: format!("{}", e) })?;
259 
260 		let thread_handle = std::thread::spawn(process_x11_events);
261 
262 		Ok(Manager {
263 			//mutex: Mutex::new(()),
264 			window,
265 			thread_handle: Some(thread_handle),
266 			callback: None,
267 			callback_result: false,
268 			data: Default::default(),
269 			image: super::common::ImageData {
270 				width: 0,
271 				height: 0,
272 				bytes: std::borrow::Cow::from(vec![]),
273 			},
274 			incr_process: false,
275 			incr_received: false,
276 			target_atom: 0,
277 			reply_data: Default::default(),
278 			reply_offset: 0,
279 		})
280 	}
281 
set_x11_selection_owner(&self, shared: &mut SharedState) -> bool282 	fn set_x11_selection_owner(&self, shared: &mut SharedState) -> bool {
283 		let clipboard_atom = shared.common_atoms().CLIPBOARD;
284 		let cookie = shared.conn.as_ref().unwrap().set_selection_owner(
285 			self.window,
286 			clipboard_atom,
287 			Time::CURRENT_TIME,
288 		);
289 
290 		cookie.is_ok()
291 	}
292 
set_image(&mut self, shared: &mut SharedState, image: ImageData) -> Result<(), Error>293 	fn set_image(&mut self, shared: &mut SharedState, image: ImageData) -> Result<(), Error> {
294 		if !self.set_x11_selection_owner(shared) {
295 			return Err(Error::Unknown {
296 				description: "Failed to set x11 selection owner.".into(),
297 			});
298 		}
299 
300 		self.image.width = image.width;
301 		self.image.height = image.height;
302 		self.image.bytes = image.bytes.into_owned().into();
303 
304 		// Put a ~nullptr~ (None) in the m_data for image/png format and then we'll
305 		// encode the png data when the image is requested in this format.
306 		self.data.insert(shared.common_atoms().MIME_IMAGE_PNG, None);
307 
308 		Ok(())
309 	}
310 
311 	/// Rust impl: instead of this function there's a more generic `set_data` which I believe can
312 	/// also set user formats, but arboard doesn't support that for now.
set_text(&mut self, shared: &mut SharedState, bytes: Vec<u8>) -> Result<(), Error>313 	fn set_text(&mut self, shared: &mut SharedState, bytes: Vec<u8>) -> Result<(), Error> {
314 		if !self.set_x11_selection_owner(shared) {
315 			return Err(Error::Unknown {
316 				description: "Could not take ownership of the x11 selection".into(),
317 			});
318 		}
319 
320 		let atoms = shared.text_atoms();
321 		if atoms.is_empty() {
322 			return Err(Error::Unknown { description:
323 				"Couldn't get the atoms that identify supported text formats for the x11 clipboard"
324 					.into(),
325 			});
326 		}
327 
328 		let arc_data = Arc::new(Mutex::new(bytes));
329 		for atom in atoms {
330 			self.data.insert(atom, Some(arc_data.clone()));
331 		}
332 
333 		Ok(())
334 	}
335 
clear_data(&mut self)336 	fn clear_data(&mut self) {
337 		self.data.clear();
338 		self.image.width = 0;
339 		self.image.height = 0;
340 		self.image.bytes = Vec::new().into();
341 	}
342 
set_requestor_property_with_clipboard_content( &mut self, shared: &mut SharedState, requestor: Window, property: Atom, target: Atom, ) -> bool343 	fn set_requestor_property_with_clipboard_content(
344 		&mut self,
345 		shared: &mut SharedState,
346 		requestor: Window,
347 		property: Atom,
348 		target: Atom,
349 	) -> bool {
350 		let item = {
351 			if let Some(item) = self.data.get_mut(&target) {
352 				item
353 			} else {
354 				// Nothing to do (unsupported target)
355 				return false;
356 			}
357 		};
358 
359 		// This can be null if the data was set from an image but we
360 		// didn't encode the image yet (e.g. to image/png format).
361 		if item.is_none() {
362 			encode_data_on_demand(shared, &mut self.image, target, item);
363 
364 			// Return nothing, the given "target" cannot be constructed
365 			// (maybe by some encoding error).
366 			if item.is_none() {
367 				return false;
368 			}
369 		}
370 
371 		let item = item.as_ref().unwrap().lock().unwrap();
372 		// Set the "property" of "requestor" with the
373 		// clipboard content in the requested format ("target").
374 		if let Err(e) = shared.conn.as_ref().unwrap().change_property8(
375 			PropMode::REPLACE,
376 			requestor,
377 			property,
378 			target,
379 			item.as_slice(),
380 		) {
381 			log::error!("{}", e)
382 		}
383 
384 		true
385 	}
386 
copy_reply_data(&mut self, reply: &GetPropertyReply)387 	fn copy_reply_data(&mut self, reply: &GetPropertyReply) {
388 		let src = &reply.value;
389 		// n = length of "src" in bytes
390 		let n = reply.value_len;
391 		let req = self.reply_offset + n as usize;
392 		match &mut self.reply_data {
393 			None => {
394 				self.reply_offset = 0; // Rust impl: I added this just to be extra sure.
395 				self.reply_data = Some(Arc::new(Mutex::new(vec![0; req])));
396 			}
397 			// The "m_reply_data" size can be smaller because the size
398 			// specified in INCR property is just a lower bound.
399 			Some(reply_data) => {
400 				let mut reply_data = reply_data.lock().unwrap();
401 				if req > reply_data.len() {
402 					reply_data.resize(req, 0);
403 				}
404 			}
405 		}
406 		let src_slice = src.as_slice();
407 		let mut reply_data_locked = self.reply_data.as_mut().unwrap().lock().unwrap();
408 		reply_data_locked[self.reply_offset..req].copy_from_slice(src_slice);
409 		self.reply_offset += n as usize;
410 	}
411 
412 	// Rust impl: It's strange, the reply attribute is also unused in the original code.
call_callback(&mut self, _reply: GetPropertyReply)413 	fn call_callback(&mut self, _reply: GetPropertyReply) {
414 		self.callback_result = false;
415 		if let Some(callback) = &self.callback {
416 			self.callback_result = callback(&self.reply_data);
417 		}
418 		CONDVAR.notify_one();
419 
420 		self.reply_data = None;
421 	}
422 
423 	/// Rust impl: This function was added instead of the destructor because the drop
424 	/// does not get called on lazy static objects. This function is registered for `libc::atexit`
425 	/// on a successful initialization
destruct()426 	fn destruct() {
427 		let join_handle;
428 
429 		// The following scope is to ensure that we release the lock
430 		// before attempting to join the thread.
431 		{
432 			let mut guard = LOCKED_OBJECTS.lock().unwrap();
433 			if guard.is_none() {
434 				return;
435 			}
436 			macro_rules! manager {
437 				() => {
438 					guard.as_mut().unwrap().manager
439 				};
440 			}
441 			macro_rules! shared {
442 				() => {
443 					guard.as_mut().unwrap().shared
444 				};
445 			}
446 
447 			if !manager!().data.is_empty()
448 				&& manager!().window != 0
449 				&& manager!().window == get_x11_selection_owner(&mut shared!())
450 			{
451 				let atoms = vec![shared!().common_atoms().SAVE_TARGETS];
452 				let selection = shared!().common_atoms().CLIPBOARD_MANAGER;
453 
454 				// Start the SAVE_TARGETS mechanism so the X11
455 				// CLIPBOARD_MANAGER will save our clipboard data
456 				// from now on.
457 				guard = get_data_from_selection_owner(
458 					guard,
459 					&atoms,
460 					Some(Arc::new(|_| true)),
461 					selection,
462 				)
463 				.1;
464 			}
465 
466 			if manager!().window != 0 {
467 				let window = manager!().window;
468 				let _ = shared!().conn.as_ref().unwrap().destroy_window(window);
469 				let _ = shared!().conn.as_ref().unwrap().flush();
470 				manager!().window = 0;
471 			}
472 			join_handle = manager!().thread_handle.take();
473 		}
474 
475 		if let Some(handle) = join_handle {
476 			handle.join().ok();
477 		}
478 
479 		// This is not needed because the connection is automatically disconnected when droped
480 		// if (m_connection)
481 		//     xcb_disconnect(m_connection);
482 	}
483 }
484 
process_x11_events()485 fn process_x11_events() {
486 	let connection = {
487 		let lo = LOCKED_OBJECTS.lock().unwrap();
488 		lo.as_ref().unwrap().shared.conn.clone()
489 	};
490 
491 	let mut stop = false;
492 	while !stop {
493 		let event = {
494 			// If this doesn't work, wrap the connection into an Arc
495 			std::thread::sleep(Duration::from_millis(5));
496 			let maybe_event = connection.as_ref().unwrap().poll_for_event();
497 			match maybe_event {
498 				Ok(Some(e)) => e,
499 				Ok(None) => continue,
500 				Err(_) => break,
501 			}
502 		};
503 		match event {
504 			Event::DestroyNotify(_) => {
505 				//println!("Received destroy event, stopping");
506 				stop = true;
507 				//panic!("{}", line!());
508 				//break;
509 			}
510 
511 			// Someone else has new content in the clipboard, so is
512 			// notifying us that we should delete our data now.
513 			Event::SelectionClear(event) => {
514 				//println!("Received selection clear,");
515 				handle_selection_clear_event(event);
516 			}
517 
518 			// Someone is requesting the clipboard content from us.
519 			Event::SelectionRequest(event) => {
520 				//println!("Received selection request");
521 				handle_selection_request_event(event);
522 			}
523 
524 			// We've requested the clipboard content and this is the
525 			// answer.
526 			Event::SelectionNotify(event) => {
527 				//println!("Received selection notify");
528 				handle_selection_notify_event(event);
529 			}
530 
531 			Event::PropertyNotify(event) => {
532 				//println!("Received property notify");
533 				handle_property_notify_event(event);
534 			}
535 			_ => {}
536 		}
537 		// The event uses RAII, so it's free'd automatically
538 	}
539 }
540 
handle_selection_clear_event(event: SelectionClearEvent)541 fn handle_selection_clear_event(event: SelectionClearEvent) {
542 	let selection = event.selection;
543 	let mut guard = LOCKED_OBJECTS.lock().unwrap();
544 	let locked = guard.as_mut().unwrap();
545 	let clipboard_atom = { locked.shared.common_atoms().CLIPBOARD };
546 	if selection == clipboard_atom {
547 		locked.manager.clear_data();
548 	}
549 }
550 
handle_selection_request_event(event: SelectionRequestEvent)551 fn handle_selection_request_event(event: SelectionRequestEvent) {
552 	let target = event.target;
553 	let requestor = event.requestor;
554 	let property = event.property;
555 	let time = event.time;
556 	let selection = event.selection;
557 	let targets_atom;
558 	let save_targets_atom;
559 	let multiple_atom;
560 	let atom_atom;
561 	{
562 		let mut guard = LOCKED_OBJECTS.lock().unwrap();
563 		let locked = guard.as_mut().unwrap();
564 		let shared = &mut locked.shared;
565 		targets_atom = shared.common_atoms().TARGETS;
566 		save_targets_atom = shared.common_atoms().SAVE_TARGETS;
567 		multiple_atom = shared.common_atoms().MULTIPLE;
568 		atom_atom = shared.common_atoms().ATOM;
569 	}
570 	if target == targets_atom {
571 		let mut targets = Atoms::with_capacity(4);
572 		targets.push(targets_atom);
573 		targets.push(save_targets_atom);
574 		targets.push(multiple_atom);
575 		let mut guard = LOCKED_OBJECTS.lock().unwrap();
576 		let locked = guard.as_mut().unwrap();
577 		let manager = &locked.manager;
578 		for atom in manager.data.keys() {
579 			targets.push(*atom);
580 		}
581 
582 		let shared = &locked.shared;
583 		// Set the "property" of "requestor" with the clipboard
584 		// formats ("targets", atoms) that we provide.
585 		if let Err(e) = shared.conn.as_ref().unwrap().change_property32(
586 			PropMode::REPLACE,
587 			requestor,
588 			property,
589 			atom_atom,
590 			targets.as_slice(),
591 		) {
592 			log::error!("{}", e);
593 		};
594 	} else if target == save_targets_atom {
595 		// Do nothing
596 	} else if target == multiple_atom {
597 		let mut guard = LOCKED_OBJECTS.lock().unwrap();
598 		let locked = guard.as_mut().unwrap();
599 		let reply = {
600 			let atom_pair_atom = locked.shared.common_atoms().ATOM_PAIR;
601 			get_and_delete_property(
602 				locked.shared.conn.as_ref().unwrap(),
603 				requestor,
604 				property,
605 				atom_pair_atom,
606 				false,
607 			)
608 		};
609 		if let Some(reply) = reply {
610 			let atoms = reply.value32();
611 			for atom in atoms.into_iter().flatten() {
612 				let target = atom;
613 				let property = atom;
614 				let property_set = locked.manager.set_requestor_property_with_clipboard_content(
615 					&mut locked.shared,
616 					requestor,
617 					property,
618 					target,
619 				);
620 				if !property_set {
621 					if let Err(e) = locked.shared.conn.as_ref().unwrap().change_property(
622 						PropMode::REPLACE,
623 						requestor,
624 						property,
625 						AtomEnum::NONE,
626 						0,
627 						0,
628 						&[],
629 					) {
630 						log::error!("{}", e)
631 					}
632 				}
633 			}
634 		}
635 	} else {
636 		let mut guard = LOCKED_OBJECTS.lock().unwrap();
637 		let locked = guard.as_mut().unwrap();
638 		let property_set = locked.manager.set_requestor_property_with_clipboard_content(
639 			&mut locked.shared,
640 			requestor,
641 			property,
642 			target,
643 		);
644 		if !property_set {
645 			return;
646 		}
647 	}
648 
649 	let mut guard = LOCKED_OBJECTS.lock().unwrap();
650 	let locked = guard.as_mut().unwrap();
651 	let shared = &mut locked.shared;
652 
653 	// Notify the "requestor" that we've already updated the property.
654 	let notify = SelectionNotifyEvent {
655 		response_type: xproto::SELECTION_NOTIFY_EVENT,
656 		sequence: 0,
657 		time,
658 		requestor,
659 		selection,
660 		target,
661 		property,
662 	};
663 	if let Err(e) =
664 		shared.conn.as_ref().unwrap().send_event(false, requestor, EventMask::NO_EVENT, notify)
665 	{
666 		log::error!("{}", e)
667 	}
668 	if let Err(e) = shared.conn.as_ref().unwrap().flush() {
669 		log::error!("{}", e)
670 	}
671 }
672 
handle_selection_notify_event(event: SelectionNotifyEvent)673 fn handle_selection_notify_event(event: SelectionNotifyEvent) {
674 	let target = event.target;
675 	let requestor = event.requestor;
676 	let property = event.property;
677 	let mut guard = LOCKED_OBJECTS.lock().unwrap();
678 	let mut locked = guard.as_mut().unwrap();
679 	assert_eq!(requestor, locked.manager.window);
680 
681 	if target == locked.shared.common_atoms().TARGETS {
682 		locked.manager.target_atom = locked.shared.common_atoms().ATOM;
683 	} else {
684 		locked.manager.target_atom = target;
685 	}
686 
687 	let target_atom = locked.manager.target_atom;
688 	let reply = get_and_delete_property(
689 		locked.shared.conn.as_ref().unwrap(),
690 		requestor,
691 		property,
692 		target_atom,
693 		true,
694 	);
695 	if let Some(reply) = reply {
696 		let reply_type = reply.type_;
697 		// In this case, We're going to receive the clipboard content in
698 		// chunks of data with several PropertyNotify events.
699 		let incr_atom = locked.shared.common_atoms().INCR;
700 		if reply_type == incr_atom {
701 			let reply = get_and_delete_property(
702 				locked.shared.conn.as_ref().unwrap(),
703 				requestor,
704 				property,
705 				incr_atom,
706 				true,
707 			);
708 			if let Some(reply) = reply {
709 				if reply.value_len == 4 {
710 					let n = reply.value32().and_then(|mut values| values.next()).unwrap_or(0);
711 					locked.manager.reply_data = Some(Arc::new(Mutex::new(vec![0u8; n as usize])));
712 					locked.manager.reply_offset = 0;
713 					locked.manager.incr_process = true;
714 					locked.manager.incr_received = true;
715 				}
716 			}
717 		} else {
718 			// Simple case, the whole clipboard content in just one reply
719 			// (without the INCR method).
720 			locked.manager.reply_data = None;
721 			locked.manager.reply_offset = 0;
722 			locked.manager.copy_reply_data(&reply);
723 			locked.manager.call_callback(reply);
724 		}
725 	}
726 }
727 
handle_property_notify_event(event: PropertyNotifyEvent)728 fn handle_property_notify_event(event: PropertyNotifyEvent) {
729 	let state = event.state;
730 	let atom = event.atom;
731 	let window = event.window;
732 	let mut guard = LOCKED_OBJECTS.lock().unwrap();
733 	let mut locked = guard.as_mut().unwrap();
734 	if locked.manager.incr_process
735 		&& state == Property::NEW_VALUE
736 		&& atom == locked.shared.common_atoms().CLIPBOARD
737 	{
738 		let target_atom = locked.manager.target_atom;
739 		let reply = get_and_delete_property(
740 			locked.shared.conn.as_ref().unwrap(),
741 			window,
742 			atom,
743 			target_atom,
744 			true,
745 		);
746 		if let Some(reply) = reply {
747 			locked.manager.incr_received = true;
748 
749 			// When the length is 0 it means that the content was
750 			// completely sent by the selection owner.
751 			if reply.value_len > 0 {
752 				locked.manager.copy_reply_data(&reply);
753 			} else {
754 				// Now that m_reply_data has the complete clipboard content,
755 				// we can call the m_callback.
756 				locked.manager.call_callback(reply);
757 				locked.manager.incr_process = false;
758 			}
759 		}
760 	}
761 }
762 
get_and_delete_property( conn: &RustConnection, window: Window, property: Atom, atom: Atom, delete_prop: bool, ) -> Option<GetPropertyReply>763 fn get_and_delete_property(
764 	conn: &RustConnection,
765 	window: Window,
766 	property: Atom,
767 	atom: Atom,
768 	delete_prop: bool,
769 ) -> Option<GetPropertyReply> {
770 	conn.get_property(
771 		delete_prop,
772 		window,
773 		property,
774 		atom,
775 		0,
776 		0x1fffffff, // 0x1fffffff = INT32_MAX / 4
777 	)
778 	.ok()
779 	.and_then(|cookie| cookie.reply().ok())
780 }
781 
get_data_from_selection_owner<'a>( mut guard: MutexGuard<'a, Option<LockedObjects>>, atoms: &[Atom], callback: NotifyCallback, mut selection: xproto::Atom, ) -> (bool, MutexGuard<'a, Option<LockedObjects>>)782 fn get_data_from_selection_owner<'a>(
783 	mut guard: MutexGuard<'a, Option<LockedObjects>>,
784 	atoms: &[Atom],
785 	callback: NotifyCallback,
786 	mut selection: xproto::Atom,
787 ) -> (bool, MutexGuard<'a, Option<LockedObjects>>) {
788 	// Wait a response for 100 milliseconds
789 	const CV_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
790 	{
791 		let locked = guard.as_mut().unwrap();
792 		if selection == 0 {
793 			selection = locked.shared.common_atoms().CLIPBOARD;
794 		}
795 		locked.manager.callback = callback;
796 
797 		// Clear data if we are not the selection owner.
798 		if locked.manager.window != get_x11_selection_owner(&mut locked.shared) {
799 			locked.manager.data.clear();
800 		}
801 	}
802 
803 	// Ask to the selection owner for its content on each known
804 	// text format/atom.
805 	for atom in atoms.iter() {
806 		{
807 			let locked = guard.as_mut().unwrap();
808 			let clipboard_atom = locked.shared.common_atoms().CLIPBOARD;
809 			if let Err(e) = locked.shared.conn.as_ref().unwrap().convert_selection(
810 				locked.manager.window,
811 				selection,
812 				*atom,
813 				clipboard_atom,
814 				Time::CURRENT_TIME,
815 			) {
816 				log::error!("{}", e)
817 			}
818 			if let Err(e) = locked.shared.conn.as_ref().unwrap().flush() {
819 				log::error!("{}", e)
820 			}
821 		}
822 
823 		// We use the "m_incr_received" to wait several timeouts in case
824 		// that we've received the INCR SelectionNotify or
825 		// PropertyNotify events.
826 		'incr_loop: loop {
827 			guard.as_mut().unwrap().manager.incr_received = false;
828 			match CONDVAR.wait_timeout(guard, CV_TIMEOUT) {
829 				Ok((new_guard, status)) => {
830 					guard = new_guard;
831 					if !status.timed_out() {
832 						// If the condition variable was notified, it means that the
833 						// callback was called correctly.
834 						return (guard.as_ref().unwrap().manager.callback_result, guard);
835 					}
836 
837 					if !guard.as_ref().unwrap().manager.incr_received {
838 						break 'incr_loop;
839 					}
840 				}
841 				Err(err) => {
842 					panic!(
843 						"A critical error occured while working with the x11 clipboard. {}",
844 						err
845 					);
846 				}
847 			}
848 		}
849 	}
850 
851 	guard.as_mut().unwrap().manager.callback = None;
852 	(false, guard)
853 }
854 
get_x11_selection_owner(shared: &mut SharedState) -> Window855 fn get_x11_selection_owner(shared: &mut SharedState) -> Window {
856 	let mut result = 0;
857 
858 	let clipboard_atom = shared.common_atoms().CLIPBOARD;
859 	let cookie = shared.conn.as_ref().unwrap().get_selection_owner(clipboard_atom);
860 	let reply = cookie.ok().and_then(|cookie| cookie.reply().ok());
861 	if let Some(reply) = reply {
862 		result = reply.owner;
863 	}
864 
865 	result
866 }
867 
get_text(mut guard: MutexGuard<Option<LockedObjects>>) -> Result<String, Error>868 fn get_text(mut guard: MutexGuard<Option<LockedObjects>>) -> Result<String, Error> {
869 	// Rust impl: This function is probably the ugliest Rust code I've ever written
870 	// Make no mistake, the original, C++ code was perfectly fine (which I didn't write)
871 	let owner = get_x11_selection_owner(&mut guard.as_mut().unwrap().shared);
872 	if owner == guard.as_mut().unwrap().manager.window {
873 		let atoms = guard.as_mut().unwrap().shared.text_atoms();
874 		for atom in atoms.iter() {
875 			let mut item = None;
876 			if let Some(Some(i)) = guard.as_mut().unwrap().manager.data.get(atom) {
877 				item = Some(i.clone());
878 			}
879 			if let Some(item) = item {
880 				// Unwrapping the item because we always initialize text with `Some`
881 				let locked = item.lock().unwrap();
882 				let result = String::from_utf8(locked.clone());
883 				return result.map_err(|_| Error::ConversionFailure);
884 			}
885 		}
886 	} else if owner != 0 {
887 		let atoms = guard.as_mut().unwrap().shared.text_atoms();
888 		let result = Arc::new(Mutex::new(Ok(String::new())));
889 		let callback = {
890 			let result = result.clone();
891 			Arc::new(move |data: &BufferPtr| {
892 				if let Some(reply_data) = data {
893 					let locked_data = reply_data.lock().unwrap();
894 					let mut locked_result = result.lock().unwrap();
895 					*locked_result = String::from_utf8(locked_data.clone());
896 				}
897 				true
898 			})
899 		};
900 
901 		let (success, _) = get_data_from_selection_owner(guard, &atoms, Some(callback as _), 0);
902 		if success {
903 			let mut taken = Ok(String::new());
904 			let mut locked = result.lock().unwrap();
905 			std::mem::swap(&mut taken, &mut locked);
906 			return taken.map_err(|_| Error::ConversionFailure);
907 		}
908 	}
909 	Err(Error::ContentNotAvailable)
910 }
911 
get_image(mut guard: MutexGuard<Option<LockedObjects>>) -> Result<ImageData, Error>912 fn get_image(mut guard: MutexGuard<Option<LockedObjects>>) -> Result<ImageData, Error> {
913 	let owner = get_x11_selection_owner(&mut guard.as_mut().unwrap().shared);
914 	//let mut result_img;
915 	if owner == guard.as_ref().unwrap().manager.window {
916 		let image = &guard.as_ref().unwrap().manager.image;
917 		if image.width > 0 && image.height > 0 && !image.bytes.is_empty() {
918 			return Ok(image.to_owned_img());
919 		}
920 	} else if owner != 0 {
921 		let atoms = vec![guard.as_mut().unwrap().shared.common_atoms().MIME_IMAGE_PNG];
922 		let result: Arc<Mutex<Result<ImageData, Error>>> =
923 			Arc::new(Mutex::new(Err(Error::ContentNotAvailable)));
924 		let callback = {
925 			let result = result.clone();
926 			Arc::new(move |data: &BufferPtr| {
927 				if let Some(reply_data) = data {
928 					let locked_data = reply_data.lock().unwrap();
929 					let cursor = std::io::Cursor::new(&*locked_data);
930 					let mut reader = image::io::Reader::new(cursor);
931 					reader.set_format(image::ImageFormat::Png);
932 					let image;
933 					match reader.decode() {
934 						Ok(img) => image = img.into_rgba8(),
935 						Err(_e) => {
936 							let mut locked_result = result.lock().unwrap();
937 							*locked_result = Err(Error::ConversionFailure);
938 							return false;
939 						}
940 					}
941 					let (w, h) = image.dimensions();
942 					let mut locked_result = result.lock().unwrap();
943 					let image_data = ImageData {
944 						width: w as usize,
945 						height: h as usize,
946 						bytes: image.into_raw().into(),
947 					};
948 					*locked_result = Ok(image_data);
949 				}
950 				true
951 			})
952 		};
953 		let _success = get_data_from_selection_owner(guard, &atoms, Some(callback as _), 0).0;
954 		// Rust impl: We return the result here no matter if it succeeded, because the result will
955 		// tell us if it hasn't
956 		let mut taken = Err(Error::Unknown {
957 			description: format!("Implementation error at {}:{}", file!(), line!()),
958 		});
959 		let mut locked = result.lock().unwrap();
960 		std::mem::swap(&mut taken, &mut locked);
961 		return taken;
962 	}
963 	Err(Error::ContentNotAvailable)
964 }
965 
encode_data_on_demand( shared: &mut SharedState, image: &mut ImageData, atom: xproto::Atom, buffer: &mut Option<Arc<Mutex<Vec<u8>>>>, )966 fn encode_data_on_demand(
967 	shared: &mut SharedState,
968 	image: &mut ImageData,
969 	atom: xproto::Atom,
970 	buffer: &mut Option<Arc<Mutex<Vec<u8>>>>,
971 ) {
972 	if atom == shared.common_atoms().MIME_IMAGE_PNG {
973 		if let Ok(image) = encode_as_png(image) {
974 			*buffer = Some(Arc::new(Mutex::new(image)));
975 		}
976 	}
977 }
978 
ensure_lo_initialized() -> Result<MutexGuard<'static, Option<LockedObjects>>, Error>979 fn ensure_lo_initialized() -> Result<MutexGuard<'static, Option<LockedObjects>>, Error> {
980 	let mut locked = LOCKED_OBJECTS.lock().unwrap();
981 	if locked.is_none() {
982 		*locked = Some(LockedObjects::new().map_err(|e| Error::Unknown {
983 			description: format!(
984 				"Could not initialize the x11 clipboard handling facilities. Cause: {}",
985 				e
986 			),
987 		})?);
988 	}
989 	Ok(locked)
990 }
991 
with_locked_objects<F, T>(action: F) -> Result<T, Error> where F: FnOnce(&mut LockedObjects) -> Result<T, Error>,992 fn with_locked_objects<F, T>(action: F) -> Result<T, Error>
993 where
994 	F: FnOnce(&mut LockedObjects) -> Result<T, Error>,
995 {
996 	// The gobal may not have been initialized yet or may have been destroyed previously.
997 	//
998 	// Note: the global objects gets destroyed (replaced with None) when the last
999 	// clipboard context is dropped (goes out of scope).
1000 	let mut locked = ensure_lo_initialized()?;
1001 	let lo = locked.as_mut().unwrap();
1002 	action(lo)
1003 }
1004 
1005 pub struct X11ClipboardContext {
1006 	_owned: Arc<Mutex<Option<LockedObjects>>>,
1007 }
1008 
1009 impl Drop for X11ClipboardContext {
drop(&mut self)1010 	fn drop(&mut self) {
1011 		// If there's no other owner than us and the global,
1012 		// then destruct the manager
1013 		if Arc::strong_count(&LOCKED_OBJECTS) == 2 {
1014 			Manager::destruct();
1015 			let mut locked = LOCKED_OBJECTS.lock().unwrap();
1016 			*locked = None;
1017 		}
1018 	}
1019 }
1020 
1021 impl X11ClipboardContext {
new() -> Self1022 	pub(crate) fn new() -> Self {
1023 		X11ClipboardContext { _owned: LOCKED_OBJECTS.clone() }
1024 	}
1025 
get_text(&mut self) -> Result<String, Error>1026 	pub(crate) fn get_text(&mut self) -> Result<String, Error> {
1027 		let locked = ensure_lo_initialized()?;
1028 		get_text(locked)
1029 	}
1030 
set_text(&mut self, text: String) -> Result<(), Error>1031 	pub(crate) fn set_text(&mut self, text: String) -> Result<(), Error> {
1032 		with_locked_objects(|locked| {
1033 			let manager = &mut locked.manager;
1034 			let shared = &mut locked.shared;
1035 			manager.set_text(shared, text.into_bytes())
1036 		})
1037 	}
1038 
get_image(&mut self) -> Result<ImageData, Error>1039 	pub(crate) fn get_image(&mut self) -> Result<ImageData, Error> {
1040 		let locked = ensure_lo_initialized()?;
1041 		get_image(locked)
1042 	}
1043 
set_image(&mut self, image: ImageData) -> Result<(), Error>1044 	pub(crate) fn set_image(&mut self, image: ImageData) -> Result<(), Error> {
1045 		with_locked_objects(|locked| {
1046 			let manager = &mut locked.manager;
1047 			let shared = &mut locked.shared;
1048 			manager.set_image(shared, image)
1049 		})
1050 	}
1051 }
1052