egui_winit/
clipboard.rs

1use raw_window_handle::RawDisplayHandle;
2
3/// Handles interfacing with the OS clipboard.
4///
5/// If the "clipboard" feature is off, or we cannot connect to the OS clipboard,
6/// then a fallback clipboard that just works within the same app is used instead.
7pub struct Clipboard {
8    #[cfg(all(feature = "arboard", not(target_os = "android")))]
9    arboard: Option<arboard::Clipboard>,
10
11    #[cfg(all(
12        any(
13            target_os = "linux",
14            target_os = "dragonfly",
15            target_os = "freebsd",
16            target_os = "netbsd",
17            target_os = "openbsd"
18        ),
19        feature = "smithay-clipboard"
20    ))]
21    smithay: Option<smithay_clipboard::Clipboard>,
22
23    /// Fallback manual clipboard.
24    clipboard: String,
25}
26
27impl Clipboard {
28    /// Construct a new instance
29    pub fn new(_raw_display_handle: Option<RawDisplayHandle>) -> Self {
30        Self {
31            #[cfg(all(feature = "arboard", not(target_os = "android")))]
32            arboard: init_arboard(),
33
34            #[cfg(all(
35                any(
36                    target_os = "linux",
37                    target_os = "dragonfly",
38                    target_os = "freebsd",
39                    target_os = "netbsd",
40                    target_os = "openbsd"
41                ),
42                feature = "smithay-clipboard"
43            ))]
44            smithay: init_smithay_clipboard(_raw_display_handle),
45
46            clipboard: Default::default(),
47        }
48    }
49
50    pub fn get(&mut self) -> Option<String> {
51        #[cfg(all(
52            any(
53                target_os = "linux",
54                target_os = "dragonfly",
55                target_os = "freebsd",
56                target_os = "netbsd",
57                target_os = "openbsd"
58            ),
59            feature = "smithay-clipboard"
60        ))]
61        if let Some(clipboard) = &mut self.smithay {
62            return match clipboard.load() {
63                Ok(text) => Some(text),
64                Err(err) => {
65                    log::error!("smithay paste error: {err}");
66                    None
67                }
68            };
69        }
70
71        #[cfg(all(feature = "arboard", not(target_os = "android")))]
72        if let Some(clipboard) = &mut self.arboard {
73            return match clipboard.get_text() {
74                Ok(text) => Some(text),
75                Err(err) => {
76                    log::error!("arboard paste error: {err}");
77                    None
78                }
79            };
80        }
81
82        Some(self.clipboard.clone())
83    }
84
85    pub fn set_text(&mut self, text: String) {
86        #[cfg(all(
87            any(
88                target_os = "linux",
89                target_os = "dragonfly",
90                target_os = "freebsd",
91                target_os = "netbsd",
92                target_os = "openbsd"
93            ),
94            feature = "smithay-clipboard"
95        ))]
96        if let Some(clipboard) = &mut self.smithay {
97            clipboard.store(text);
98            return;
99        }
100
101        #[cfg(all(feature = "arboard", not(target_os = "android")))]
102        if let Some(clipboard) = &mut self.arboard {
103            if let Err(err) = clipboard.set_text(text) {
104                log::error!("arboard copy/cut error: {err}");
105            }
106            return;
107        }
108
109        self.clipboard = text;
110    }
111
112    pub fn set_image(&mut self, image: &egui::ColorImage) {
113        #[cfg(all(feature = "arboard", not(target_os = "android")))]
114        if let Some(clipboard) = &mut self.arboard {
115            if let Err(err) = clipboard.set_image(arboard::ImageData {
116                width: image.width(),
117                height: image.height(),
118                bytes: std::borrow::Cow::Borrowed(bytemuck::cast_slice(&image.pixels)),
119            }) {
120                log::error!("arboard copy/cut error: {err}");
121            }
122            log::debug!("Copied image to clipboard");
123            return;
124        }
125
126        log::error!("Copying images is not supported. Enable the 'clipboard' feature of `egui-winit` to enable it.");
127        _ = image;
128    }
129}
130
131#[cfg(all(feature = "arboard", not(target_os = "android")))]
132fn init_arboard() -> Option<arboard::Clipboard> {
133    profiling::function_scope!();
134
135    log::trace!("Initializing arboard clipboard…");
136    match arboard::Clipboard::new() {
137        Ok(clipboard) => Some(clipboard),
138        Err(err) => {
139            log::warn!("Failed to initialize arboard clipboard: {err}");
140            None
141        }
142    }
143}
144
145#[cfg(all(
146    any(
147        target_os = "linux",
148        target_os = "dragonfly",
149        target_os = "freebsd",
150        target_os = "netbsd",
151        target_os = "openbsd"
152    ),
153    feature = "smithay-clipboard"
154))]
155fn init_smithay_clipboard(
156    raw_display_handle: Option<RawDisplayHandle>,
157) -> Option<smithay_clipboard::Clipboard> {
158    #![allow(clippy::undocumented_unsafe_blocks)]
159
160    profiling::function_scope!();
161
162    if let Some(RawDisplayHandle::Wayland(display)) = raw_display_handle {
163        log::trace!("Initializing smithay clipboard…");
164        #[allow(unsafe_code)]
165        Some(unsafe { smithay_clipboard::Clipboard::new(display.display.as_ptr()) })
166    } else {
167        #[cfg(feature = "wayland")]
168        log::debug!("Cannot init smithay clipboard without a Wayland display handle");
169        #[cfg(not(feature = "wayland"))]
170        log::debug!(
171            "Cannot init smithay clipboard: the 'wayland' feature of 'egui-winit' is not enabled"
172        );
173        None
174    }
175}