Rust · 3369 bytes Raw Blame History
1 //! Wayland clipboard access via wl-clipboard-rs
2 //!
3 //! Uses the wlr-data-control or ext-data-control protocol for clipboard access
4 //! without needing a Wayland surface (perfect for daemon use).
5
6 use std::io::Read;
7
8 use wl_clipboard_rs::copy::{MimeType as CopyMimeType, Options as CopyOptions, Source};
9 use wl_clipboard_rs::paste::{
10 get_contents, get_mime_types, ClipboardType, MimeType as PasteMimeType, Seat,
11 };
12
13 use super::ClipboardError;
14
15 /// Read clipboard content for a specific MIME type
16 ///
17 /// If `mime_type` is None, reads with any available MIME type.
18 /// Returns the data and the actual MIME type used.
19 pub fn read_clipboard(mime_type: Option<&str>) -> Result<(Vec<u8>, String), ClipboardError> {
20 let mime = match mime_type {
21 Some(mt) => PasteMimeType::Specific(mt),
22 None => PasteMimeType::Any,
23 };
24
25 let (mut pipe, actual_mime) = get_contents(ClipboardType::Regular, Seat::Unspecified, mime)
26 .map_err(|e| ClipboardError::Wayland(format!("Failed to get clipboard contents: {}", e)))?;
27
28 let mut data = Vec::new();
29 pipe.read_to_end(&mut data)
30 .map_err(|e| ClipboardError::Io(e))?;
31
32 if data.is_empty() {
33 return Err(ClipboardError::Empty);
34 }
35
36 Ok((data, actual_mime.to_string()))
37 }
38
39 /// Get available MIME types from clipboard
40 pub fn get_available_mime_types() -> Result<Vec<String>, ClipboardError> {
41 let mime_types = get_mime_types(ClipboardType::Regular, Seat::Unspecified)
42 .map_err(|e| ClipboardError::Wayland(format!("Failed to get MIME types: {}", e)))?;
43
44 Ok(mime_types.into_iter().map(|m| m.to_string()).collect())
45 }
46
47 /// Set clipboard content with a specific MIME type
48 pub fn set_clipboard(data: &[u8], mime_type: &str) -> Result<(), ClipboardError> {
49 let mut opts = CopyOptions::new();
50
51 // Fork to background so the clipboard persists after we return
52 opts.foreground(false);
53 opts.copy(
54 Source::Bytes(data.to_vec().into_boxed_slice()),
55 CopyMimeType::Specific(mime_type.to_string()),
56 )
57 .map_err(|e| ClipboardError::Wayland(format!("Failed to set clipboard: {}", e)))?;
58
59 Ok(())
60 }
61
62 /// MIME type priority lists for selection
63 pub const IMAGE_MIME_PRIORITY: &[&str] = &["image/png", "image/jpeg", "image/webp", "image/gif"];
64
65 pub const TEXT_MIME_PRIORITY: &[&str] = &[
66 "text/plain;charset=utf-8",
67 "text/plain",
68 "UTF8_STRING",
69 "STRING",
70 "TEXT",
71 ];
72
73 /// Select the best MIME type from offered types based on priority
74 pub fn select_mime_type(offered: &[String], sync_images: bool, sync_text: bool) -> Option<String> {
75 // Try images first if enabled (higher priority for screenshots)
76 if sync_images {
77 for pref in IMAGE_MIME_PRIORITY {
78 if offered.iter().any(|m| m == *pref) {
79 return Some(pref.to_string());
80 }
81 }
82 }
83
84 // Then text if enabled
85 if sync_text {
86 for pref in TEXT_MIME_PRIORITY {
87 if offered.iter().any(|m| m == *pref) {
88 return Some(pref.to_string());
89 }
90 }
91 }
92
93 None
94 }
95
96 /// Check if a MIME type is an image type
97 pub fn is_image_mime(mime: &str) -> bool {
98 mime.starts_with("image/")
99 }
100
101 /// Check if a MIME type is a text type
102 pub fn is_text_mime(mime: &str) -> bool {
103 mime.starts_with("text/") || mime == "UTF8_STRING" || mime == "STRING" || mime == "TEXT"
104 }
105