druid_shell/clipboard.rs
1// Copyright 2019 The Druid Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Interacting with the system pasteboard/clipboard.
16pub use crate::backend::clipboard as backend;
17
18/// A handle to the system clipboard.
19///
20/// To get access to the global clipboard, call [`Application::clipboard`].
21///
22///
23/// # Working with text
24///
25/// Copying and pasting text is simple, using [`Clipboard::put_string`] and
26/// [`Clipboard::get_string`]. If this is all you need, you're in luck.
27///
28/// # Advanced usage
29///
30/// When working with data more complicated than plaintext, you will generally
31/// want to make that data available in multiple formats.
32///
33/// For instance, if you are writing an image editor, you may have a preferred
34/// private format, that preserves metadata or layer information; but in order
35/// to interoperate with your user's other programs, you might also make your
36/// data available as an SVG, for other editors, and a bitmap image for applications
37/// that can accept general image data.
38///
39/// ## `FormatId`entifiers
40///
41/// In order for other applications to find data we put on the clipboard,
42/// (and for us to use data from other applications) we need to use agreed-upon
43/// identifiers for our data types. On macOS, these should be
44/// [`Universal Type Identifier`]s; on other platforms they appear to be
45/// mostly [MIME types]. Several common types are exposed as constants on
46/// [`ClipboardFormat`], these `const`s are set per-platform.
47///
48/// When defining custom formats, you should use the correct identifier for
49/// the current platform.
50///
51/// ## Setting custom data
52///
53/// To put custom data on the clipboard, you create a [`ClipboardFormat`] for
54/// each type of data you support. You are responsible for ensuring that the
55/// data is already correctly serialized.
56///
57///
58/// ### `ClipboardFormat` for text
59///
60/// If you wish to put text on the clipboard in addition to other formats,
61/// take special care to use `ClipboardFormat::TEXT` as the [`FormatId`]. On
62/// windows, we treat this identifier specially, and make sure the data is
63/// encoded as a wide string; all other data going into and out of the
64/// clipboard is treated as an array of bytes.
65///
66/// # Examples
67///
68/// ## Getting and setting text:
69///
70/// ```no_run
71/// use druid_shell::{Application, Clipboard};
72///
73/// let mut clipboard = Application::global().clipboard();
74/// clipboard.put_string("watch it there pal");
75/// if let Some(contents) = clipboard.get_string() {
76/// assert_eq!("what it there pal", contents.as_str());
77/// }
78///
79/// ```
80///
81/// ## Copying multi-format data
82///
83/// ```no_run
84/// use druid_shell::{Application, Clipboard, ClipboardFormat};
85///
86/// let mut clipboard = Application::global().clipboard();
87///
88/// let custom_type_id = "io.xieditor.path-clipboard-type";
89///
90/// let formats = [
91/// ClipboardFormat::new(custom_type_id, make_custom_data()),
92/// ClipboardFormat::new(ClipboardFormat::SVG, make_svg_data()),
93/// ClipboardFormat::new(ClipboardFormat::PDF, make_pdf_data()),
94/// ];
95///
96/// clipboard.put_formats(&formats);
97///
98/// # fn make_custom_data() -> Vec<u8> { unimplemented!() }
99/// # fn make_svg_data() -> Vec<u8> { unimplemented!() }
100/// # fn make_pdf_data() -> Vec<u8> { unimplemented!() }
101/// ```
102/// ## Supporting multi-format paste
103///
104/// ```no_run
105/// use druid_shell::{Application, Clipboard, ClipboardFormat};
106///
107/// let clipboard = Application::global().clipboard();
108///
109/// let custom_type_id = "io.xieditor.path-clipboard-type";
110/// let supported_types = &[custom_type_id, ClipboardFormat::SVG, ClipboardFormat::PDF];
111/// let best_available_type = clipboard.preferred_format(supported_types);
112///
113/// if let Some(format) = best_available_type {
114/// let data = clipboard.get_format(format).expect("I promise not to unwrap in production");
115/// do_something_with_data(format, data)
116/// }
117///
118/// # fn do_something_with_data(_: &str, _: Vec<u8>) {}
119/// ```
120///
121/// [`Application::clipboard`]: crate::Application::clipboard
122/// [`Universal Type Identifier`]: https://escapetech.eu/manuals/qdrop/uti.html
123/// [MIME types]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
124#[derive(Debug, Clone)]
125pub struct Clipboard(pub(crate) backend::Clipboard);
126
127impl Clipboard {
128 /// Put a string onto the system clipboard.
129 pub fn put_string(&mut self, s: impl AsRef<str>) {
130 self.0.put_string(s);
131 }
132
133 /// Put multi-format data on the system clipboard.
134 pub fn put_formats(&mut self, formats: &[ClipboardFormat]) {
135 self.0.put_formats(formats)
136 }
137
138 /// Get a string from the system clipboard, if one is available.
139 pub fn get_string(&self) -> Option<String> {
140 self.0.get_string()
141 }
142
143 /// Given a list of supported clipboard types, returns the supported type which has
144 /// highest priority on the system clipboard, or `None` if no types are supported.
145 pub fn preferred_format(&self, formats: &[FormatId]) -> Option<FormatId> {
146 self.0.preferred_format(formats)
147 }
148
149 /// Return data in a given format, if available.
150 ///
151 /// It is recommended that the [`FormatId`] argument be a format returned by
152 /// [`Clipboard::preferred_format`].
153 ///
154 /// [`Clipboard::preferred_format`]: struct.Clipboard.html#method.preferred_format
155 /// [`FormatId`]: type.FormatId.html
156 pub fn get_format(&self, format: FormatId) -> Option<Vec<u8>> {
157 self.0.get_format(format)
158 }
159
160 /// For debugging: print the resolved identifiers for each type currently
161 /// on the clipboard.
162 #[doc(hidden)]
163 pub fn available_type_names(&self) -> Vec<String> {
164 self.0.available_type_names()
165 }
166}
167
168/// A type identifier for the system clipboard.
169///
170/// These should be [`UTI` strings] on macOS, and (by convention?) [MIME types] elsewhere.
171///
172/// [`UTI` strings]: https://escapetech.eu/manuals/qdrop/uti.html
173/// [MIME types]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
174pub type FormatId = &'static str;
175
176/// Data coupled with a type identifier.
177#[derive(Debug, Clone)]
178#[cfg_attr(feature = "wayland", allow(dead_code))]
179#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
180pub struct ClipboardFormat {
181 pub(crate) identifier: FormatId,
182 pub(crate) data: Vec<u8>,
183}
184
185impl ClipboardFormat {
186 /// Create a new `ClipboardFormat` with the given `FormatId` and bytes.
187 ///
188 /// You are responsible for ensuring that this data can be interpreted
189 /// as the provided format.
190 pub fn new(identifier: FormatId, data: impl Into<Vec<u8>>) -> Self {
191 let data = data.into();
192 ClipboardFormat { identifier, data }
193 }
194}
195
196impl From<String> for ClipboardFormat {
197 fn from(src: String) -> ClipboardFormat {
198 let data = src.into_bytes();
199 ClipboardFormat::new(ClipboardFormat::TEXT, data)
200 }
201}
202
203impl From<&str> for ClipboardFormat {
204 fn from(src: &str) -> ClipboardFormat {
205 src.to_string().into()
206 }
207}
208
209impl From<backend::Clipboard> for Clipboard {
210 fn from(src: backend::Clipboard) -> Clipboard {
211 Clipboard(src)
212 }
213}
214
215cfg_if::cfg_if! {
216 if #[cfg(target_os = "macos")] {
217 impl ClipboardFormat {
218 pub const PDF: &'static str = "com.adobe.pdf";
219 pub const TEXT: &'static str = "public.utf8-plain-text";
220 pub const SVG: &'static str = "public.svg-image";
221 }
222 } else {
223 impl ClipboardFormat {
224 cfg_if::cfg_if! {
225 if #[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "openbsd"))] {
226 // trial and error; this is the most supported string type for gtk?
227 pub const TEXT: &'static str = "UTF8_STRING";
228 } else {
229 pub const TEXT: &'static str = "text/plain";
230 }
231 }
232 pub const PDF: &'static str = "application/pdf";
233 pub const SVG: &'static str = "image/svg+xml";
234 }
235 }
236}