wingy_core/data_transfer.rs
1//! Cross-platform abstractions related to data transfer (i.e. clipboard and drag-and-drop).
2//!
3//! > **NOTE**: Interacting with the clipboard is currently implemented on Wayland, X11, macOS
4//! > and Windows.
5//!
6//! # Quickstart
7//!
8//! The API in this module is used for both sending and receiving data. The flow is detailed below,
9//! but to quickly get started, the relevant APIs are the following:
10//!
11//! ### Receiving a drag-and-drop operation
12//!
13//! - [`DragEntered`](crate::event::WindowEvent::DragEntered) - informs a window that a new drag
14//! operation has started.
15//! - [`data_transfer`](crate::event_loop::ActiveEventLoop::data_transfer) - get metadata about the
16//! incoming transfer.
17//! - [`DataTransfer`] - metadata about the incoming transfer, in particular the available types
18//! - [`set_valid_dnd_actions`](crate::event_loop::ActiveEventLoop::set_valid_dnd_actions) - the
19//! application must set at least some actions as valid in order for the drag to be considered
20//! accepted.
21//! - [`fetch_data_transfer`](crate::event_loop::ActiveEventLoop::fetch_data_transfer) - request the
22//! actual data, with a specific type, from the data transfer.
23//! - [`DataTransferReceived`](crate::event::WindowEvent::DataTransferReceived) - the actual data,
24//! with a specific type, has been received.
25//! - [`TypedData`] - provides methods to read the actual data
26//!
27//! ### Sending a drag-and-drop operation
28//!
29//! - [`DataTransferSend`] - the core trait which defines data to be sent
30//! - [`DataTransferSendBuilder`] - helper to create a new outgoing data transfer from a set of
31//! types and callbacks that supply data of that type
32//! - [`ActiveEventLoop::start_drag`](crate::event_loop::ActiveEventLoop::start_drag) - the
33//! application calls this to start a new drag operation
34//! - [`OutgoingDragDropped`](crate::event::WindowEvent::OutgoingDragDropped)/
35//! [`OutgoingDragCanceled`](crate::event::WindowEvent::OutgoingDragCanceled) - the application
36//! receives this when the user has ended the drag operation, by dropping the data or by canceling
37//! the operation respectively
38//!
39//! # Detailed flow
40//!
41//! ## Receiving a drag-and-drop operation
42//!
43//! On all platforms, the process looks something like this:
44//!
45//! - A data transfer advertises a set of types which the data can be interpreted as. While the
46//! precise implementation depends on platform, there's a set of types which can be safely
47//! transferred between applications on all platforms (see [`TypeHint`]).
48//! - For example, if you copy or drag text from a web page, the browser may advertise the text
49//! formatted using HTML, the text formatted as RTF, and the text with all formatting removed
50//! simultaneously.
51//! - An application receiving a data transfer chooses one or more types that it understands and
52//! requests the data in those formats (in practice, it will usually only request a single
53//! format).
54//! - The source application converts the data stored in its memory to the requested format and
55//! asynchronously sends it to the target application
56//!
57//! On some platforms, the data is sometimes available synchronously, but all platforms have at
58//! least some method of sending the data asynchronously and some types of data that may _only_ be
59//! sent using the asynchronous interface. Because of this, the API in wingy must be asynchronous.
60//!
61//! The flow for a user application that implements drag-and-drop would look something like this:
62//!
63//! - The application receives a [`DragEntered`](crate::event::WindowEvent::DragEntered) event. This
64//! event supplies a [`DataTransferId`] which can be used to request information or operations on
65//! the dragged data by using methods on [`Window`](crate::window::Window).
66//! - To make sure that the operating system displays the correct cursor, and that modifier keys
67//! will change the selected drag action correctly, the application should call
68//! [`set_valid_dnd_actions`](crate::event_loop::ActiveEventLoop::set_valid_dnd_actions). See
69//! documentation on that method for details.
70//! - As the drag operation continues, the window will receive
71//! [`DragPosition`](crate::event::WindowEvent::DragPosition) events.
72//! - At any point during this operation, the receiving application may request either the available
73//! types or even the data being transferred. This may be useful in cases where the application
74//! wants to preload the data. For example, an image editor may want to display the image on the
75//! canvas during the drag operation.
76//! - When the user tries to drop the data onto the window, that window will receive either a
77//! [`DragDropped`](crate::event::WindowEvent::DragDropped) or
78//! [`DragLeft`](crate::event::WindowEvent::DragLeft) event if the drag operation was accepted or
79//! rejected, respectively. See documentation for
80//! [`set_valid_dnd_actions`](crate::event_loop::ActiveEventLoop::set_valid_dnd_actions) for
81//! details on accepting/rejecting a drag.
82//!
83//! ## Sending a drag-and-drop operation
84//!
85//! As the source application cannot interact with the ongoing drag while it is in-flight, this flow
86//! is a lot simpler.
87//!
88//! - The application creates a [`DataTransferSend`] with a set of types and associated data. For
89//! most cases, this can be done with [`DataTransferSendBuilder`].
90//! - The application passes this [`DataTransferSend`] to
91//! [`ActiveEventLoop::start_drag`](crate::event_loop::ActiveEventLoop::start_drag)`. This is also
92//! where metadata is set, such as the icon that will be shown during the drag operation.
93//! - When the drag operation completes, the application receives
94//! [`OutgoingDragDropped`](crate::event::WindowEvent::OutgoingDragDropped) with the resultant
95//! action, or [`OutgoingDragCanceled`](crate::event::WindowEvent::OutgoingDragCanceled), and
96//! handles it appropriately. For example, if the drag was successful and the operation is
97//! [`DndAction::Move`](crate::event_loop::DndAction::Move), then the application would delete the
98//! source object, since the data has now been transferred somewhere else.
99
100#![warn(missing_docs)]
101
102use std::any::Any;
103use std::ops::ControlFlow;
104use std::{fmt, io};
105
106/// Unique identifier for a data transfer.
107#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
108pub struct DataTransferId(i64);
109
110impl DataTransferId {
111 /// Convert the [`DataTransferId`] into the underlying integer.
112 ///
113 /// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic.
114 pub const fn into_raw(self) -> i64 {
115 self.0
116 }
117
118 /// Construct a [`DataTransferId`] from the underlying integer.
119 ///
120 /// This should only be called with integers returned from [`DataTransferId::into_raw`].
121 pub const fn from_raw(id: i64) -> Self {
122 Self(id)
123 }
124}
125
126/// The set of types supported cross-platform.
127#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
128#[non_exhaustive]
129pub enum TypeHint {
130 /// Plain UTF-8 text (see [`TypedData::try_as_string`]).
131 ///
132 /// **Note for platform implementations**: this hint is _only_ for UTF-8 text. If the platform
133 /// returns plaintext in some format other than UTF-8 by default, a [`TypedData`]
134 /// implementation marked with this type hint should convert to UTF-8.
135 Plaintext,
136 /// A list of URIs in the format defined by the `text/uri-list` MIME type, encoded as UTF-8 (see
137 /// [`TypedData::try_as_uris`]).
138 ///
139 /// **Note for platform implementations**: this hint is _only_ for URIs encoded precisely in the
140 /// format specified above. If the platform uses a different format, a [`TypedData`]
141 /// implementation marked with this type hint should convert to that format.
142 UriList,
143 /// A HTML-formatted string
144 Html,
145 /// An RTF-formatted string
146 Rtf,
147 /// Audio
148 Audio {
149 /// An optional hint for the encoding of the supplied bytes, specified using the standard
150 /// file extension for that audio format, lowercase and without the leading `.`.
151 extension_hint: Option<&'static str>,
152 },
153 /// Image data
154 Image {
155 /// An optional hint for the encoding of the supplied bytes, specified using the standard
156 /// file extension for that image format, lowercase and without the leading `.`.
157 extension_hint: Option<&'static str>,
158 },
159}
160
161impl TypeHint {
162 /// Check whether the two type hints "match".
163 ///
164 /// This is subtly different to direct equality. If one of the types is an image or audio with a
165 /// `None` extension hint, then the other type just needs to match variant (i.e. image/audio),
166 /// the extension does not also have to be `None`.
167 pub fn matches(&self, other: &Self) -> bool {
168 match (self, other) {
169 (Self::Plaintext, Self::Plaintext)
170 | (Self::UriList, Self::UriList)
171 | (Self::Html, Self::Html)
172 | (Self::Rtf, Self::Rtf) => true,
173
174 (
175 Self::Audio { extension_hint: this_ext },
176 Self::Audio { extension_hint: other_ext },
177 )
178 | (
179 Self::Image { extension_hint: this_ext },
180 Self::Image { extension_hint: other_ext },
181 ) => match (this_ext, other_ext) {
182 (Some(this_ext), Some(other_ext)) => this_ext == other_ext,
183 (None, _) | (_, None) => true,
184 },
185
186 _ => false,
187 }
188 }
189}
190
191/// The type of a data transfer.
192///
193/// [`hint`](TransferType::hint) can be called to get the type in
194/// a cross-platform format (see [`TypeHint`])
195pub trait TransferType: Any + fmt::Debug {
196 /// Get the cross-platform representation of this type.
197 ///
198 /// If this returns `None`, then this is a platform-dependent type that has no cross-platform
199 /// equivalent.
200 fn hint(&self) -> Option<TypeHint>;
201
202 /// Check whether two dynamically-typed transfer types are equivalent.
203 // Can't use a `PartialEq` bound because it causes a dependency cycle.
204 fn matches(&self, other: &dyn TransferType) -> bool;
205}
206
207impl TransferType for TypeHint {
208 fn hint(&self) -> Option<TypeHint> {
209 Some(*self)
210 }
211
212 fn matches(&self, other: &dyn TransferType) -> bool {
213 other.hint().is_some_and(|hint| self.matches(&hint))
214 }
215}
216
217impl_dyn_casting!(TransferType);
218
219/// Data that has been fetched from a data transfer
220///
221/// ### Blocking
222///
223/// Note that this type provides a blocking interface. In cases where reading this type directly on
224/// the event loop would cause a deadlock, the backend will make a best-effort attempt to return an
225/// error with [`io::ErrorKind::Deadlock`]. For now, the only way to access the data is via blocking
226/// on the event loop, so simply retrying the next time an event is received that references the
227/// data transfer should be enough to ensure that the data is accessible.
228pub trait TypedData: Any + fmt::Debug + Send + Sync {
229 /// The type of this `TypedData`.
230 fn type_(&self) -> &dyn TransferType;
231
232 /// If this value is readable as bytes, return a reader than can be used to read those bytes.
233 ///
234 /// On some platforms, the reader must be driven incrementally upon each
235 /// [`WindowEvent::DataTransferReceived`](crate::event::WindowEvent::DataTransferReceived)`. If
236 /// you don't need to stream the data and just want the bytes in a single buffer, use
237 /// [`TypedData::try_as_bytes`].
238 fn try_read(&self) -> Option<Box<dyn io::BufRead>>;
239
240 /// If this value is readable as bytes, return those bytes.
241 ///
242 /// If this returns [`WouldBlock`](std::io::ErrorKind::WouldBlock), then it should be called
243 /// again upon next receiving
244 /// [`WindowEvent::DataTransferReceived`](crate::event::WindowEvent::DataTransferReceived)
245 fn try_as_bytes(&self) -> io::Result<Vec<u8>> {
246 let mut reader = self
247 .try_read()
248 .ok_or_else(|| io::Error::other("This `TypedData` is not readable as bytes"))?;
249
250 let mut out = Vec::new();
251
252 reader.read_to_end(&mut out)?;
253
254 Ok(out)
255 }
256
257 /// Read this value as a list of URIs.
258 ///
259 /// If this value is not readable as URIs, return an error.
260 ///
261 /// The returned `String`s should be interpreted as URIs conforming to [RFC 3986](https://www.rfc-editor.org/info/rfc3986/).
262 ///
263 /// If this returns [`WouldBlock`](std::io::ErrorKind::WouldBlock), then it should be called
264 /// again upon next receiving
265 /// [`WindowEvent::DataTransferReceived`](crate::event::WindowEvent::DataTransferReceived)
266 fn try_as_uris(&self) -> io::Result<Vec<String>>;
267
268 /// Read this value as a plain text string.
269 ///
270 /// If this value is not readable as a string, return an error.
271 ///
272 /// If this returns [`WouldBlock`](std::io::ErrorKind::WouldBlock), then it should be called
273 /// again upon next receiving
274 /// [`WindowEvent::DataTransferReceived`](crate::event::WindowEvent::DataTransferReceived)
275 fn try_as_string(&self) -> io::Result<String>;
276}
277
278// Required for `WindowEvent` to implement `PartialEq` - we just implement this on a best-effort
279// basis.
280impl PartialEq for dyn TypedData {
281 fn eq(&self, other: &Self) -> bool {
282 std::ptr::addr_eq(self, other)
283 }
284}
285
286impl_dyn_casting!(TypedData);
287
288/// Metadata about a data transfer. This does not allow actually receiving data, as that is an
289/// asynchronous operation. To fetch the data from the source application, see
290/// [`ActiveEventLoop::fetch_data_transfer`](crate::event_loop::ActiveEventLoop::fetch_data_transfer).
291pub trait DataTransfer: Any + fmt::Debug {
292 /// Iterate over each type advertized by this `DataTransfer`. This is just a minor optimization,
293 /// in most cases you should probably use [`has_type`](DataTransfer::has_type) or
294 /// [`available_types`](DataTransfer::available_types).
295 fn for_each_available_type<'this>(
296 &'this self,
297 func: &'_ mut dyn FnMut(&'this dyn TransferType) -> ControlFlow<()>,
298 );
299
300 /// Display the list of all available types.
301 ///
302 /// This is useful if more-complex type matching is required, but for most cases
303 /// [`has_type`](DataTransfer::has_type) should be used.
304 fn available_types(&self) -> Vec<&'_ dyn TransferType> {
305 let mut out = Vec::new();
306
307 self.for_each_available_type(&mut |ty| {
308 out.push(ty);
309 ControlFlow::Continue(())
310 });
311
312 out
313 }
314
315 /// Check if the supplied type is provided by this [`DataTransfer`].
316 ///
317 /// Supplying a [`TypeHint`] as the type is supported on all platforms, but if some
318 /// platform-specific type is required then that platform's implementation of `TransferType` can
319 /// be used.
320 fn has_type(&self, type_: &dyn TransferType) -> bool {
321 let mut found = false;
322 self.for_each_available_type(&mut |haystack| {
323 if haystack.matches(type_) {
324 found = true;
325 ControlFlow::Break(())
326 } else {
327 ControlFlow::Continue(())
328 }
329 });
330
331 found
332 }
333}
334
335impl_dyn_casting!(DataTransfer);
336
337/// Kinds of data that can be sent via a `DataTransfer`.
338///
339/// Some kinds of data cannot be represented by just a binary blob in a cross-platform way.
340/// File URIs on Windows and macOS are represented as arrays of strings, and strings have
341/// different encoding on different platforms. To allow this to be represented, we allow
342/// supplying strings and URIs separately from binary blobs.
343#[derive(Debug, Clone, PartialEq, Eq, Hash)]
344#[non_exhaustive]
345pub enum SendData {
346 /// List of URIs.
347 ///
348 /// These should conform to [RFC 3986](https://www.rfc-editor.org/info/rfc3986/). To send file
349 /// paths, convert them to `file:` URIs (for example with the [`url`](https://docs.rs/url/2)
350 /// crate) and pass the resulting strings here.
351 ///
352 /// Note that `SendData` implements `From<String>` and `From<Vec<u8>>`, but _not_
353 /// `From<Vec<String>>`, as it is not necessarily obvious to a reader that `Vec<String>`
354 /// will be interpreted as a URI list.
355 Uris(Vec<String>),
356 /// String
357 ///
358 /// This can also be constructed with the [`From<String>`](std::string::String) implementation.
359 String(String),
360 /// Binary blob
361 ///
362 /// This can also be constructed with the [`From<Vec<u8>>`](std::vec::Vec) implementation.
363 Bytes(Vec<u8>),
364}
365
366// We monomorphize these `From` implementations instead of making them generic, in order to
367// prevent accidentally casting to the wrong type.
368impl From<String> for SendData {
369 fn from(value: String) -> Self {
370 Self::String(value)
371 }
372}
373
374impl From<Vec<u8>> for SendData {
375 fn from(value: Vec<u8>) -> Self {
376 Self::Bytes(value)
377 }
378}
379
380/// Trait for sending data via a data transfer.
381///
382/// See [`ActiveEventLoop::start_drag`](crate::event_loop::ActiveEventLoop::start_drag) for where
383/// this is used. To build an implementation of this trait dynamically in a cross-platform way, use
384/// [`DataTransferSendBuilder`].
385pub trait DataTransferSend: DataTransfer + Send {
386 /// Get the data for the specified type, or `None` if this value does not supply the given data
387 /// type.
388 fn data_for_type(&self, type_: &dyn TransferType) -> Option<SendData>;
389}
390
391impl_dyn_casting!(DataTransferSend);
392
393type SendDataCallback<T> = Box<dyn Fn(&T, &dyn TransferType) -> Option<SendData> + Send>;
394
395/// Dynamic builder for an implementation of [`DataTransferSend`].
396///
397/// On all platforms, inter-application data transfer (i.e. clipboard and drag-and-drop) works like
398/// so:
399///
400/// - The source advertises a set of types that it can transfer.
401/// - The destination picks one or more of those types to receive.
402/// - The source sends the data for that type.
403///
404/// This type abstracts that in a way that allows data to be sent cross-platform. `T` is an optional
405/// state value, which allows the user to have a single source of truth for their data, converting
406/// it lazily to the requested type.
407pub struct DataTransferSendBuilder<T> {
408 state: T,
409 types: Vec<(Box<dyn TransferType + Send>, SendDataCallback<T>)>,
410}
411
412impl<T> fmt::Debug for DataTransferSendBuilder<T>
413where
414 T: fmt::Debug,
415{
416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 f.debug_struct("NewDataTransferBuilder").field("state", &self.state).finish_non_exhaustive()
418 }
419}
420
421impl<T> DataTransfer for DataTransferSendBuilder<T>
422where
423 T: fmt::Debug + Send + 'static,
424{
425 fn for_each_available_type<'this>(
426 &'this self,
427 func: &'_ mut dyn FnMut(&'this dyn TransferType) -> ControlFlow<()>,
428 ) {
429 let _ = self.types.iter().try_for_each(|(ty, _)| func(&**ty));
430 }
431}
432
433impl<T> DataTransferSend for DataTransferSendBuilder<T>
434where
435 T: fmt::Debug + Send + 'static,
436{
437 fn data_for_type(&self, type_: &dyn TransferType) -> Option<SendData> {
438 self.data_for_type(type_)
439 }
440}
441
442impl<T> DataTransferSendBuilder<T> {
443 /// Create a new [`DataTransferSendBuilder`], with a state value which acts as
444 /// the single source of truth for the underlying data.
445 pub fn new(state: T) -> Self {
446 Self { state, types: vec![] }
447 }
448}
449
450impl<T> DataTransferSendBuilder<T> {
451 fn data_for_type(&self, type_: &dyn TransferType) -> Option<SendData> {
452 let (_, func) = self.types.iter().find(|(ty, _)| ty.matches(type_))?;
453
454 func(&self.state, type_)
455 }
456
457 /// Add a callback which converts the builder's state to the given type. In
458 /// most cases, `type_` will be [`TypeHint`].
459 pub fn add_type<Ty, F, O>(&mut self, type_: Ty, func: F) -> &mut Self
460 where
461 Ty: TransferType + Send,
462 F: Fn(&T, &dyn TransferType) -> Option<O> + Send + 'static,
463 O: Into<SendData>,
464 {
465 self.types
466 .push((Box::new(type_), Box::new(move |state, ty| func(state, ty).map(Into::into))));
467 self
468 }
469
470 /// Return a new builder, adding a callback which converts the builder's state
471 /// to the given type.
472 ///
473 /// For cross-platform use, `type_` will be [`TypeHint`]. The closure additionally receives
474 /// a [`TransferType`], which is not necessarily the same as `type_` for the following reasons:
475 ///
476 /// - The OS may have multiple types which are equivalent to the supplied type
477 /// - `TypeHint::Audio` and `TypeHint::Image` with `extension_hint: None` will advertise all
478 /// supported audio and image formats, in which case the closure may receive a type with an
479 /// extension chosen by the receiving application.
480 pub fn with_type<Ty, F, O>(mut self, type_: Ty, func: F) -> Self
481 where
482 Ty: TransferType + Send,
483 F: Fn(&T, &dyn TransferType) -> Option<O> + Send + 'static,
484 O: Into<SendData>,
485 {
486 self.add_type(type_, func);
487 self
488 }
489}
490
491impl<T> DataTransferSendBuilder<T>
492where
493 T: fmt::Debug + Send + 'static,
494{
495 /// Consume the builder, returning an implementation of [`DataTransferSend`].
496 ///
497 /// Note that this is only provided for explicitness and ergonomics. [`DataTransferSendBuilder`]
498 /// implements [`DataTransferSend`] and this method is equivalent to [`Box::new`].
499 pub fn build(self) -> Box<dyn DataTransferSend> {
500 Box::new(self)
501 }
502}