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
//! Access the clipboard.
use crate::dnd;
use std::path::PathBuf;
use std::sync::Arc;
/// A set of clipboard requests.
#[derive(Debug)]
pub struct Clipboard {
/// The read requests the runtime must fulfill.
pub reads: Vec<Kind>,
/// The content that must be written to the clipboard by the runtime,
/// if any.
pub write: Option<Content>,
/// Pending DnD requests from widgets.
pub dnd_requests: Vec<dnd::Request>,
/// Whether a widget has asked to read the primary selection.
///
/// A flag rather than a `Vec<Kind>` like `reads`: the primary selection is
/// text and only text, so there is no format to choose between. Kept
/// separate from `reads` because [`Event::Read`] carries no `Kind`, so a
/// primary read arriving on that channel would be indistinguishable from
/// an ordinary paste — and any focused text input would swallow it.
pub primary_reads: bool,
/// Text to publish as the primary selection, if any.
pub primary_write: Option<String>,
}
impl Clipboard {
/// Creates a new empty set of [`Clipboard`] requests.
pub fn new() -> Self {
Self {
reads: Vec::new(),
write: None,
dnd_requests: Vec::new(),
primary_reads: false,
primary_write: None,
}
}
/// Merges the current [`Clipboard`] requests with others.
pub fn merge(&mut self, other: &mut Self) {
self.reads.append(&mut other.reads);
self.write = other.write.take().or(self.write.take());
self.dnd_requests.append(&mut other.dnd_requests);
self.primary_reads |= other.primary_reads;
// Last writer in the frame wins, matching how `write` merges. Copy on
// select fires on every mouse release, so same-frame collisions are
// ordinary rather than exceptional.
self.primary_write = other.primary_write.take().or(self.primary_write.take());
}
}
impl Default for Clipboard {
fn default() -> Self {
Self::new()
}
}
/// A clipboard event.
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
/// The clipboard was read.
Read(Result<Arc<Content>, Error>),
/// The clipboard was written.
Written(Result<(), Error>),
/// The primary selection was read.
///
/// Its own variant so a widget can tell a middle-click paste from an
/// ordinary one; [`Event::Read`] carries no discriminator.
PrimaryRead(Result<Arc<String>, Error>),
/// The primary selection was written.
PrimaryWritten(Result<(), Error>),
}
/// Some clipboard content.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
#[non_exhaustive]
pub enum Content {
Text(String),
Html(String),
#[cfg(feature = "image")]
Image(Image),
Files(Vec<PathBuf>),
}
impl From<String> for Content {
fn from(text: String) -> Self {
Self::Text(text)
}
}
#[cfg(feature = "image")]
impl From<Image> for Content {
fn from(image: Image) -> Self {
Self::Image(image)
}
}
impl From<Vec<PathBuf>> for Content {
fn from(files: Vec<PathBuf>) -> Self {
Self::Files(files)
}
}
/// The kind of some clipboard [`Content`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
#[non_exhaustive]
pub enum Kind {
Text,
Html,
#[cfg(feature = "image")]
Image,
Files,
}
/// A clipboard image.
#[cfg(feature = "image")]
#[derive(Debug, Clone, PartialEq)]
pub struct Image {
/// The pixels of the image in RGBA format.
pub rgba: crate::Bytes,
/// The physical [`Size`](crate::Size) of the image.
pub size: crate::Size<u32>,
}
/// A clipboard error.
#[derive(Debug, Clone, PartialEq)]
pub enum Error {
/// The clipboard in the current environment is either not present or could not be accessed.
ClipboardUnavailable,
/// The native clipboard is not accessible due to being held by another party.
ClipboardOccupied,
/// The clipboard contents were not available in the requested format.
/// This could either be due to the clipboard being empty or the clipboard contents having
/// an incompatible format to the requested one
ContentNotAvailable,
/// The image or the text that was about the be transferred to/from the clipboard could not be
/// converted to the appropriate format.
ConversionFailure,
/// Any error that doesn't fit the other error types.
Unknown {
/// A description only meant to help the developer that should not be relied on as a
/// means to identify an error case during runtime.
description: Arc<String>,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn merging_keeps_a_primary_read_from_either_side() {
// Reads are a flag, not a queue: two widgets asking in one frame want
// the same answer, and reading twice would prompt the compositor twice.
let mut a = Clipboard::new();
let mut b = Clipboard::new();
b.primary_reads = true;
a.merge(&mut b);
assert!(a.primary_reads);
}
#[test]
fn the_last_primary_write_in_a_frame_wins() {
// Copy-on-select fires on every mouse release, so two writes in one
// frame is ordinary. Matching how `write` merges keeps the two
// channels behaving the same.
let mut a = Clipboard::new();
a.primary_write = Some("first".to_owned());
let mut b = Clipboard::new();
b.primary_write = Some("second".to_owned());
a.merge(&mut b);
assert_eq!(a.primary_write.as_deref(), Some("second"));
}
#[test]
fn merging_an_empty_request_leaves_a_pending_primary_write_alone() {
let mut a = Clipboard::new();
a.primary_write = Some("keep me".to_owned());
a.merge(&mut Clipboard::new());
assert_eq!(a.primary_write.as_deref(), Some("keep me"));
}
#[test]
fn the_primary_channel_is_independent_of_the_clipboard() {
// The whole point of the separate channel: copy-on-select must not
// disturb what the user last copied deliberately.
let mut a = Clipboard::new();
let mut b = Clipboard::new();
b.primary_write = Some("selected".to_owned());
a.merge(&mut b);
assert!(a.write.is_none());
assert!(a.reads.is_empty());
}
}