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