arboard/
common.rs

1/*
2SPDX-License-Identifier: Apache-2.0 OR MIT
3
4Copyright 2022 The Arboard contributors
5
6The project to which this file belongs is licensed under either of
7the Apache 2.0 or the MIT license at the licensee's choice. The terms
8and conditions of the chosen license apply to this file.
9*/
10
11#[cfg(feature = "image-data")]
12use std::borrow::Cow;
13
14/// An error that might happen during a clipboard operation.
15///
16/// Note that both the `Display` and the `Debug` trait is implemented for this type in such a way
17/// that they give a short human-readable description of the error; however the documentation
18/// gives a more detailed explanation for each error kind.
19#[non_exhaustive]
20pub enum Error {
21	/// The clipboard contents were not available in the requested format.
22	/// This could either be due to the clipboard being empty or the clipboard contents having
23	/// an incompatible format to the requested one (eg when calling `get_image` on text)
24	ContentNotAvailable,
25
26	/// The selected clipboard is not supported by the current configuration (system and/or environment).
27	///
28	/// This can be caused by a few conditions:
29	/// - Using the Primary clipboard with an older Wayland compositor (that doesn't support version 2)
30	/// - Using the Secondary clipboard on Wayland
31	ClipboardNotSupported,
32
33	/// The native clipboard is not accessible due to being held by an other party.
34	///
35	/// This "other party" could be a different process or it could be within
36	/// the same program. So for example you may get this error when trying
37	/// to interact with the clipboard from multiple threads at once.
38	///
39	/// Note that it's OK to have multiple `Clipboard` instances. The underlying
40	/// implementation will make sure that the native clipboard is only
41	/// opened for transferring data and then closed as soon as possible.
42	ClipboardOccupied,
43
44	/// The image or the text that was about the be transferred to/from the clipboard could not be
45	/// converted to the appropriate format.
46	ConversionFailure,
47
48	/// Any error that doesn't fit the other error types.
49	///
50	/// The `description` field is only meant to help the developer and should not be relied on as a
51	/// means to identify an error case during runtime.
52	Unknown { description: String },
53}
54
55impl std::fmt::Display for Error {
56	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57		match self {
58			Error::ContentNotAvailable => f.write_str("The clipboard contents were not available in the requested format or the clipboard is empty."),
59			Error::ClipboardNotSupported => f.write_str("The selected clipboard is not supported with the current system configuration."),
60			Error::ClipboardOccupied => f.write_str("The native clipboard is not accessible due to being held by an other party."),
61			Error::ConversionFailure => f.write_str("The image or the text that was about the be transferred to/from the clipboard could not be converted to the appropriate format."),
62			Error::Unknown { description } => f.write_fmt(format_args!("Unknown error while interacting with the clipboard: {description}")),
63		}
64	}
65}
66
67impl std::error::Error for Error {}
68
69impl std::fmt::Debug for Error {
70	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71		use Error::*;
72		macro_rules! kind_to_str {
73			($( $e: pat ),*) => {
74				match self {
75					$(
76						$e => stringify!($e),
77					)*
78				}
79			}
80		}
81		let name = kind_to_str!(
82			ContentNotAvailable,
83			ClipboardNotSupported,
84			ClipboardOccupied,
85			ConversionFailure,
86			Unknown { .. }
87		);
88		f.write_fmt(format_args!("{name} - \"{self}\""))
89	}
90}
91
92impl Error {
93	pub(crate) fn unknown<M: Into<String>>(message: M) -> Self {
94		Error::Unknown { description: message.into() }
95	}
96}
97
98/// Stores pixel data of an image.
99///
100/// Each element in `bytes` stores the value of a channel of a single pixel.
101/// This struct stores four channels (red, green, blue, alpha) so
102/// a `3*3` image is going to be stored on `3*3*4 = 36` bytes of data.
103///
104/// The pixels are in row-major order meaning that the second pixel
105/// in `bytes` (starting at the fifth byte) corresponds to the pixel that's
106/// sitting to the right side of the top-left pixel (x=1, y=0)
107///
108/// Assigning a `2*1` image would for example look like this
109/// ```
110/// use arboard::ImageData;
111/// use std::borrow::Cow;
112/// let bytes = [
113///     // A red pixel
114///     255, 0, 0, 255,
115///
116///     // A green pixel
117///     0, 255, 0, 255,
118/// ];
119/// let img = ImageData {
120///     width: 2,
121///     height: 1,
122///     bytes: Cow::from(bytes.as_ref())
123/// };
124/// ```
125#[cfg(feature = "image-data")]
126#[derive(Debug, Clone)]
127pub struct ImageData<'a> {
128	pub width: usize,
129	pub height: usize,
130	pub bytes: Cow<'a, [u8]>,
131}
132
133#[cfg(feature = "image-data")]
134impl ImageData<'_> {
135	/// Returns a the bytes field in a way that it's guaranteed to be owned.
136	/// It moves the bytes if they are already owned and clones them if they are borrowed.
137	pub fn into_owned_bytes(self) -> Cow<'static, [u8]> {
138		self.bytes.into_owned().into()
139	}
140
141	/// Returns an image data that is guaranteed to own its bytes.
142	/// It moves the bytes if they are already owned and clones them if they are borrowed.
143	pub fn to_owned_img(&self) -> ImageData<'static> {
144		ImageData {
145			width: self.width,
146			height: self.height,
147			bytes: self.bytes.clone().into_owned().into(),
148		}
149	}
150}
151
152#[cfg(any(windows, all(unix, not(target_os = "macos"))))]
153pub(crate) struct ScopeGuard<F: FnOnce()> {
154	callback: Option<F>,
155}
156
157#[cfg(any(windows, all(unix, not(target_os = "macos"))))]
158impl<F: FnOnce()> ScopeGuard<F> {
159	#[cfg_attr(all(windows, not(feature = "image-data")), allow(dead_code))]
160	pub(crate) fn new(callback: F) -> Self {
161		ScopeGuard { callback: Some(callback) }
162	}
163}
164
165#[cfg(any(windows, all(unix, not(target_os = "macos"))))]
166impl<F: FnOnce()> Drop for ScopeGuard<F> {
167	fn drop(&mut self) {
168		if let Some(callback) = self.callback.take() {
169			(callback)();
170		}
171	}
172}
173
174/// Common trait for sealing platform extension traits.
175pub(crate) mod private {
176	pub trait Sealed {}
177
178	impl Sealed for crate::Get<'_> {}
179	impl Sealed for crate::Set<'_> {}
180	impl Sealed for crate::Clear<'_> {}
181}