1use crate::CapabilitySupport;
9use incular_core::Offset;
10use std::{
11 collections::HashSet,
12 fmt,
13 path::PathBuf,
14 sync::{Arc, OnceLock},
15};
16
17#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
23pub struct MediaType(String);
24
25impl MediaType {
26 pub fn new(value: impl Into<String>) -> Result<Self, TransferDataError> {
27 let value = value.into();
28 if value.is_empty()
29 || value.trim() != value
30 || value.chars().any(|character| {
31 character.is_control() || character.is_whitespace() || character == '\0'
32 })
33 {
34 return Err(TransferDataError::InvalidMediaType(value));
35 }
36 Ok(Self(value))
37 }
38
39 #[must_use]
40 pub fn as_str(&self) -> &str {
41 &self.0
42 }
43}
44
45impl fmt::Display for MediaType {
46 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
47 formatter.write_str(&self.0)
48 }
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
53pub enum TransferFormat {
54 PlainText,
55 Html,
56 UriList,
57 Files,
58 Rgba8Image,
59 Custom(MediaType),
60}
61
62impl TransferFormat {
63 #[must_use]
64 pub fn media_type(&self) -> &str {
65 match self {
66 Self::PlainText => "text/plain;charset=utf-8",
67 Self::Html => "text/html",
68 Self::UriList => "text/uri-list",
69 Self::Files => "application/x-incular-file-list",
70 Self::Rgba8Image => "application/x-incular-rgba8",
71 Self::Custom(media_type) => media_type.as_str(),
72 }
73 }
74}
75
76#[derive(Clone, Debug, PartialEq, Eq)]
78pub struct TransferImage {
79 width: u32,
80 height: u32,
81 rgba: Arc<[u8]>,
82}
83
84impl TransferImage {
85 pub fn new(
86 width: u32,
87 height: u32,
88 rgba: impl Into<Arc<[u8]>>,
89 ) -> Result<Self, TransferDataError> {
90 let rgba = rgba.into();
91 let expected = u64::from(width)
92 .checked_mul(u64::from(height))
93 .and_then(|pixels| pixels.checked_mul(4))
94 .and_then(|bytes| usize::try_from(bytes).ok())
95 .ok_or(TransferDataError::InvalidImageDimensions { width, height })?;
96 if rgba.len() != expected {
97 return Err(TransferDataError::InvalidImageLength {
98 width,
99 height,
100 actual: rgba.len(),
101 expected,
102 });
103 }
104 Ok(Self {
105 width,
106 height,
107 rgba,
108 })
109 }
110
111 #[must_use]
112 pub const fn width(&self) -> u32 {
113 self.width
114 }
115
116 #[must_use]
117 pub const fn height(&self) -> u32 {
118 self.height
119 }
120
121 #[must_use]
122 pub fn rgba(&self) -> &[u8] {
123 &self.rgba
124 }
125
126 #[must_use]
127 pub fn shared_rgba(&self) -> Arc<[u8]> {
128 self.rgba.clone()
129 }
130}
131
132#[derive(Clone, Debug, PartialEq, Eq)]
134pub enum TransferData {
135 Text(Arc<str>),
136 UriList(Arc<[String]>),
137 Files(Arc<[PathBuf]>),
138 Rgba8Image(TransferImage),
139 Bytes(Arc<[u8]>),
140}
141
142impl TransferData {
143 #[must_use]
144 pub fn text(value: impl Into<String>) -> Self {
145 Self::Text(Arc::<str>::from(value.into()))
146 }
147
148 #[must_use]
149 pub fn uri_list(values: impl IntoIterator<Item = impl Into<String>>) -> Self {
150 Self::UriList(
151 values
152 .into_iter()
153 .map(Into::into)
154 .collect::<Vec<_>>()
155 .into(),
156 )
157 }
158
159 #[must_use]
160 pub fn files(values: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
161 Self::Files(
162 values
163 .into_iter()
164 .map(Into::into)
165 .collect::<Vec<_>>()
166 .into(),
167 )
168 }
169
170 #[must_use]
171 pub fn bytes(value: impl Into<Arc<[u8]>>) -> Self {
172 Self::Bytes(value.into())
173 }
174}
175
176#[derive(Clone, Debug, PartialEq, Eq)]
177pub enum TransferDataError {
178 InvalidMediaType(String),
179 InvalidImageDimensions {
180 width: u32,
181 height: u32,
182 },
183 InvalidImageLength {
184 width: u32,
185 height: u32,
186 actual: usize,
187 expected: usize,
188 },
189 DuplicateFormat(TransferFormat),
190 TypeMismatch {
191 format: TransferFormat,
192 data: &'static str,
193 },
194 ProviderFailed(String),
195}
196
197impl fmt::Display for TransferDataError {
198 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
199 match self {
200 Self::InvalidMediaType(value) => {
201 write!(formatter, "invalid transfer media type {value:?}")
202 }
203 Self::InvalidImageDimensions { width, height } => {
204 write!(
205 formatter,
206 "RGBA8 image dimensions overflow: {width}x{height}"
207 )
208 }
209 Self::InvalidImageLength {
210 width,
211 height,
212 actual,
213 expected,
214 } => write!(
215 formatter,
216 "RGBA8 image {width}x{height} requires {expected} bytes, got {actual}"
217 ),
218 Self::DuplicateFormat(format) => {
219 write!(formatter, "duplicate transfer representation {format:?}")
220 }
221 Self::TypeMismatch { format, data } => write!(
222 formatter,
223 "transfer representation {format:?} cannot materialize {data} data"
224 ),
225 Self::ProviderFailed(message) => {
226 write!(formatter, "transfer provider failed: {message}")
227 }
228 }
229 }
230}
231
232impl std::error::Error for TransferDataError {}
233
234struct LazyTransferData {
235 provider: Arc<dyn Fn() -> Result<TransferData, TransferDataError> + Send + Sync>,
236 value: OnceLock<Result<TransferData, TransferDataError>>,
237}
238
239impl LazyTransferData {
240 fn materialize(&self) -> Result<TransferData, TransferDataError> {
241 self.value.get_or_init(|| (self.provider)()).clone()
242 }
243}
244
245#[derive(Clone)]
246enum TransferPayload {
247 Immediate(TransferData),
248 Lazy(Arc<LazyTransferData>),
249}
250
251impl TransferPayload {
252 fn materialize(&self) -> Result<TransferData, TransferDataError> {
253 match self {
254 Self::Immediate(data) => Ok(data.clone()),
255 Self::Lazy(data) => data.materialize(),
256 }
257 }
258}
259
260impl fmt::Debug for TransferPayload {
261 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
262 match self {
263 Self::Immediate(data) => formatter.debug_tuple("Immediate").field(data).finish(),
264 Self::Lazy(_) => formatter.write_str("Lazy(<provider>)"),
265 }
266 }
267}
268
269impl PartialEq for TransferPayload {
270 fn eq(&self, other: &Self) -> bool {
271 match (self, other) {
272 (Self::Immediate(left), Self::Immediate(right)) => left == right,
273 (Self::Lazy(left), Self::Lazy(right)) => Arc::ptr_eq(left, right),
274 _ => false,
275 }
276 }
277}
278
279impl Eq for TransferPayload {}
280
281#[derive(Clone, Debug, PartialEq, Eq)]
283pub struct TransferRepresentation {
284 format: TransferFormat,
285 payload: TransferPayload,
286}
287
288impl TransferRepresentation {
289 fn immediate(format: TransferFormat, data: TransferData) -> Self {
290 debug_assert!(data_matches_format(&format, &data));
291 Self {
292 format,
293 payload: TransferPayload::Immediate(data),
294 }
295 }
296
297 #[must_use]
298 pub fn plain_text(value: impl Into<String>) -> Self {
299 Self::immediate(TransferFormat::PlainText, TransferData::text(value))
300 }
301
302 #[must_use]
303 pub fn html(value: impl Into<String>) -> Self {
304 Self::immediate(TransferFormat::Html, TransferData::text(value))
305 }
306
307 #[must_use]
308 pub fn uri_list(values: impl IntoIterator<Item = impl Into<String>>) -> Self {
309 Self::immediate(TransferFormat::UriList, TransferData::uri_list(values))
310 }
311
312 #[must_use]
313 pub fn files(values: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
314 Self::immediate(TransferFormat::Files, TransferData::files(values))
315 }
316
317 #[must_use]
318 pub fn rgba8_image(image: TransferImage) -> Self {
319 Self::immediate(TransferFormat::Rgba8Image, TransferData::Rgba8Image(image))
320 }
321
322 #[must_use]
323 pub fn custom(media_type: MediaType, bytes: impl Into<Arc<[u8]>>) -> Self {
324 Self::immediate(
325 TransferFormat::Custom(media_type),
326 TransferData::bytes(bytes),
327 )
328 }
329
330 #[must_use]
334 pub fn lazy(
335 format: TransferFormat,
336 provider: impl Fn() -> Result<TransferData, TransferDataError> + Send + Sync + 'static,
337 ) -> Self {
338 Self {
339 format,
340 payload: TransferPayload::Lazy(Arc::new(LazyTransferData {
341 provider: Arc::new(provider),
342 value: OnceLock::new(),
343 })),
344 }
345 }
346
347 #[must_use]
348 pub const fn format(&self) -> &TransferFormat {
349 &self.format
350 }
351
352 pub fn materialize(&self) -> Result<TransferData, TransferDataError> {
353 let data = self.payload.materialize()?;
354 if data_matches_format(&self.format, &data) {
355 Ok(data)
356 } else {
357 Err(TransferDataError::TypeMismatch {
358 format: self.format.clone(),
359 data: transfer_data_name(&data),
360 })
361 }
362 }
363}
364
365fn data_matches_format(format: &TransferFormat, data: &TransferData) -> bool {
366 matches!(
367 (format, data),
368 (
369 TransferFormat::PlainText | TransferFormat::Html,
370 TransferData::Text(_)
371 ) | (TransferFormat::UriList, TransferData::UriList(_))
372 | (TransferFormat::Files, TransferData::Files(_))
373 | (TransferFormat::Rgba8Image, TransferData::Rgba8Image(_))
374 | (TransferFormat::Custom(_), TransferData::Bytes(_))
375 )
376}
377
378fn transfer_data_name(data: &TransferData) -> &'static str {
379 match data {
380 TransferData::Text(_) => "text",
381 TransferData::UriList(_) => "URI list",
382 TransferData::Files(_) => "file list",
383 TransferData::Rgba8Image(_) => "RGBA8 image",
384 TransferData::Bytes(_) => "bytes",
385 }
386}
387
388#[derive(Clone, Debug, PartialEq, Eq)]
390pub struct TransferItem {
391 representations: Arc<[TransferRepresentation]>,
392}
393
394impl TransferItem {
395 pub fn new(
396 representations: impl IntoIterator<Item = TransferRepresentation>,
397 ) -> Result<Self, TransferDataError> {
398 let representations = representations.into_iter().collect::<Vec<_>>();
399 let mut formats = HashSet::with_capacity(representations.len());
400 for representation in &representations {
401 if !formats.insert(representation.format.clone()) {
402 return Err(TransferDataError::DuplicateFormat(
403 representation.format.clone(),
404 ));
405 }
406 }
407 Ok(Self {
408 representations: representations.into(),
409 })
410 }
411
412 #[must_use]
413 pub fn single(representation: TransferRepresentation) -> Self {
414 Self {
415 representations: vec![representation].into(),
416 }
417 }
418
419 #[must_use]
420 pub fn representations(&self) -> &[TransferRepresentation] {
421 &self.representations
422 }
423
424 #[must_use]
425 pub fn representation(&self, format: &TransferFormat) -> Option<&TransferRepresentation> {
426 self.representations
427 .iter()
428 .find(|representation| representation.format() == format)
429 }
430
431 fn filtered(&self, request: &TransferReadRequest) -> Option<Self> {
432 let representations = self
433 .representations
434 .iter()
435 .filter(|representation| request.includes(representation.format()))
436 .cloned()
437 .collect::<Vec<_>>();
438 (!representations.is_empty()).then(|| Self {
439 representations: representations.into(),
440 })
441 }
442}
443
444#[derive(Clone, Debug, Default, PartialEq, Eq)]
446pub struct DataTransfer {
447 items: Arc<[TransferItem]>,
448}
449
450impl DataTransfer {
451 #[must_use]
452 pub fn new(items: impl IntoIterator<Item = TransferItem>) -> Self {
453 Self {
454 items: items.into_iter().collect::<Vec<_>>().into(),
455 }
456 }
457
458 #[must_use]
459 pub fn single(item: TransferItem) -> Self {
460 Self {
461 items: vec![item].into(),
462 }
463 }
464
465 #[must_use]
466 pub fn plain_text(value: impl Into<String>) -> Self {
467 Self::single(TransferItem::single(TransferRepresentation::plain_text(
468 value,
469 )))
470 }
471
472 #[must_use]
473 pub fn files(paths: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
474 Self::single(TransferItem::single(TransferRepresentation::files(paths)))
475 }
476
477 #[must_use]
478 pub fn items(&self) -> &[TransferItem] {
479 &self.items
480 }
481
482 #[must_use]
483 pub fn is_empty(&self) -> bool {
484 self.items.is_empty()
485 }
486
487 #[must_use]
488 pub fn representation(&self, format: &TransferFormat) -> Option<&TransferRepresentation> {
489 self.items
490 .iter()
491 .find_map(|item| item.representation(format))
492 }
493
494 pub fn materialize(
495 &self,
496 format: &TransferFormat,
497 ) -> Result<Option<TransferData>, TransferDataError> {
498 self.representation(format)
499 .map(TransferRepresentation::materialize)
500 .transpose()
501 }
502
503 pub fn plain_text_value(&self) -> Result<Option<Arc<str>>, TransferDataError> {
504 match self.materialize(&TransferFormat::PlainText)? {
505 Some(TransferData::Text(text)) => Ok(Some(text)),
506 Some(data) => Err(TransferDataError::TypeMismatch {
507 format: TransferFormat::PlainText,
508 data: transfer_data_name(&data),
509 }),
510 None => Ok(None),
511 }
512 }
513
514 #[must_use]
515 pub fn formats(&self) -> Vec<TransferFormat> {
516 let mut formats = Vec::new();
517 for item in self.items.iter() {
518 for representation in item.representations.iter() {
519 if !formats.contains(&representation.format) {
520 formats.push(representation.format.clone());
521 }
522 }
523 }
524 formats
525 }
526
527 #[must_use]
528 pub fn filtered(&self, request: &TransferReadRequest) -> Self {
529 Self::new(self.items.iter().filter_map(|item| item.filtered(request)))
530 }
531}
532
533#[derive(Clone, Debug, Default, PartialEq, Eq)]
536pub struct TransferReadRequest {
537 formats: Arc<[TransferFormat]>,
538}
539
540impl TransferReadRequest {
541 #[must_use]
542 pub fn all() -> Self {
543 Self::default()
544 }
545
546 #[must_use]
547 pub fn format(format: TransferFormat) -> Self {
548 Self {
549 formats: vec![format].into(),
550 }
551 }
552
553 #[must_use]
554 pub fn formats(formats: impl IntoIterator<Item = TransferFormat>) -> Self {
555 let mut unique = Vec::new();
556 for format in formats {
557 if !unique.contains(&format) {
558 unique.push(format);
559 }
560 }
561 Self {
562 formats: unique.into(),
563 }
564 }
565
566 #[must_use]
567 pub fn requested_formats(&self) -> &[TransferFormat] {
568 &self.formats
569 }
570
571 #[must_use]
572 pub fn includes(&self, format: &TransferFormat) -> bool {
573 self.formats.is_empty() || self.formats.iter().any(|requested| requested == format)
574 }
575}
576
577#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
579pub struct TransferFormatCapabilities {
580 pub plain_text: CapabilitySupport,
581 pub html: CapabilitySupport,
582 pub uri_list: CapabilitySupport,
583 pub files: CapabilitySupport,
584 pub rgba8_image: CapabilitySupport,
585 pub custom: CapabilitySupport,
586}
587
588impl TransferFormatCapabilities {
589 #[must_use]
590 pub const fn support(self, format: &TransferFormat) -> CapabilitySupport {
591 match format {
592 TransferFormat::PlainText => self.plain_text,
593 TransferFormat::Html => self.html,
594 TransferFormat::UriList => self.uri_list,
595 TransferFormat::Files => self.files,
596 TransferFormat::Rgba8Image => self.rgba8_image,
597 TransferFormat::Custom(_) => self.custom,
598 }
599 }
600
601 #[must_use]
602 pub const fn all(support: CapabilitySupport) -> Self {
603 Self {
604 plain_text: support,
605 html: support,
606 uri_list: support,
607 files: support,
608 rgba8_image: support,
609 custom: support,
610 }
611 }
612}
613
614#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
615pub struct ClipboardCapabilities {
616 pub read: TransferFormatCapabilities,
617 pub write: TransferFormatCapabilities,
618 pub lazy_write: CapabilitySupport,
621}
622
623#[derive(Clone, Debug, PartialEq, Eq)]
624pub struct ClipboardWriteReport {
625 pub written: Vec<TransferFormat>,
626 pub skipped: Vec<TransferFormat>,
627}
628
629impl ClipboardWriteReport {
630 #[must_use]
631 pub fn all_written(formats: Vec<TransferFormat>) -> Self {
632 Self {
633 written: formats,
634 skipped: Vec::new(),
635 }
636 }
637}
638
639#[derive(Clone, Debug, PartialEq, Eq)]
640pub enum ClipboardError {
641 Unavailable,
642 UnsupportedFormat(TransferFormat),
643 UnsupportedCombination,
644 Transfer(TransferDataError),
645 Backend(String),
646}
647
648impl fmt::Display for ClipboardError {
649 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
650 match self {
651 Self::Unavailable => formatter.write_str("clipboard content is unavailable"),
652 Self::UnsupportedFormat(format) => {
653 write!(formatter, "clipboard format {format:?} is unsupported")
654 }
655 Self::UnsupportedCombination => {
656 formatter.write_str("clipboard cannot represent this combination atomically")
657 }
658 Self::Transfer(error) => error.fmt(formatter),
659 Self::Backend(message) => write!(formatter, "clipboard backend failed: {message}"),
660 }
661 }
662}
663
664impl std::error::Error for ClipboardError {
665 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
666 match self {
667 Self::Transfer(error) => Some(error),
668 _ => None,
669 }
670 }
671}
672
673impl From<TransferDataError> for ClipboardError {
674 fn from(value: TransferDataError) -> Self {
675 Self::Transfer(value)
676 }
677}
678
679pub trait Clipboard {
681 fn capabilities(&self) -> ClipboardCapabilities;
682
683 fn read(&mut self, request: TransferReadRequest) -> Result<DataTransfer, ClipboardError>;
684
685 fn write(&mut self, transfer: DataTransfer) -> Result<ClipboardWriteReport, ClipboardError>;
686
687 fn get_text(&mut self) -> Option<String> {
689 let transfer = self
690 .read(TransferReadRequest::format(TransferFormat::PlainText))
691 .ok()?;
692 transfer
693 .plain_text_value()
694 .ok()?
695 .map(|text| text.to_string())
696 }
697
698 fn set_text(&mut self, text: String) {
700 let _ = self.write(DataTransfer::plain_text(text));
701 }
702}
703
704#[derive(Default)]
705pub struct MemoryClipboard {
706 transfer: Option<DataTransfer>,
707}
708
709impl Clipboard for MemoryClipboard {
710 fn capabilities(&self) -> ClipboardCapabilities {
711 ClipboardCapabilities {
712 read: TransferFormatCapabilities::all(CapabilitySupport::Supported),
713 write: TransferFormatCapabilities::all(CapabilitySupport::Supported),
714 lazy_write: CapabilitySupport::Supported,
715 }
716 }
717
718 fn read(&mut self, request: TransferReadRequest) -> Result<DataTransfer, ClipboardError> {
719 let transfer = self.transfer.as_ref().ok_or(ClipboardError::Unavailable)?;
720 let filtered = transfer.filtered(&request);
721 if filtered.is_empty() {
722 Err(ClipboardError::Unavailable)
723 } else {
724 Ok(filtered)
725 }
726 }
727
728 fn write(&mut self, transfer: DataTransfer) -> Result<ClipboardWriteReport, ClipboardError> {
729 let formats = transfer.formats();
730 self.transfer = Some(transfer);
731 Ok(ClipboardWriteReport::all_written(formats))
732 }
733}
734
735#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
737pub enum TransferOperation {
738 Copy,
739 Move,
740 Link,
741}
742
743#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
745pub struct TransferOperations(u8);
746
747impl TransferOperations {
748 pub const COPY: Self = Self(1 << 0);
749 pub const MOVE: Self = Self(1 << 1);
750 pub const LINK: Self = Self(1 << 2);
751
752 #[must_use]
753 pub const fn empty() -> Self {
754 Self(0)
755 }
756
757 #[must_use]
758 pub const fn contains(self, operation: TransferOperation) -> bool {
759 let bit = match operation {
760 TransferOperation::Copy => Self::COPY.0,
761 TransferOperation::Move => Self::MOVE.0,
762 TransferOperation::Link => Self::LINK.0,
763 };
764 self.0 & bit != 0
765 }
766
767 #[must_use]
768 pub const fn union(self, other: Self) -> Self {
769 Self(self.0 | other.0)
770 }
771
772 #[must_use]
773 pub const fn preferred(self) -> Option<TransferOperation> {
774 if self.contains(TransferOperation::Copy) {
775 Some(TransferOperation::Copy)
776 } else if self.contains(TransferOperation::Move) {
777 Some(TransferOperation::Move)
778 } else if self.contains(TransferOperation::Link) {
779 Some(TransferOperation::Link)
780 } else {
781 None
782 }
783 }
784}
785
786#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
787pub enum ExternalDragPhase {
788 Enter,
789 Over,
790 Leave,
791 Cancel,
792 Drop,
793}
794
795#[derive(Clone, Debug, PartialEq)]
799pub struct ExternalDragEvent {
800 pub phase: ExternalDragPhase,
801 pub transfer: DataTransfer,
802 pub position: Offset,
803 pub allowed_operations: TransferOperations,
804}
805
806impl ExternalDragEvent {
807 #[must_use]
808 pub fn files(
809 phase: ExternalDragPhase,
810 paths: impl IntoIterator<Item = impl Into<PathBuf>>,
811 position: Offset,
812 ) -> Self {
813 Self {
814 phase,
815 transfer: DataTransfer::files(paths),
816 position,
817 allowed_operations: TransferOperations::COPY,
821 }
822 }
823}
824
825#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
828pub struct ExternalDragResponse {
829 pub requested_operation: Option<TransferOperation>,
830}